1

I am trying to wrap my mind around C++ 20 concept and constraint by porting some of my old code.

struct Status
{
    std::string status;
    std::time_t statusDate;
};

struct CurrentStatusStack
{
    std::vector<Status> statusVec;
    std::vector<std::filesystem::path> ticketPathVec;
};

void setBar(CurrentStatusStack s)
{
    ui->setLatestStatusMessage(s.statusVec.back().status);
    ui->setLatestStatusMessagePath(s.ticketPathVec.back());
}

To translate the above code block to a similar but generic function setFoo(const T& t) and to make sure T type implements function that setBar requires I wrote a few concepts:

  1. A ContainerOf concept
/*
  `ContainerOf` requires container (could be a vector or a set
  or other STL-like containers) to implement at least:
  1. `cbegin()` that returns a const iterator pointer to the first element.
  2. `empty()` an emptiness check fn.
*/
template <class Container, typename T>
concept ContainerOf = requires(Container a, T)
{
  requires std::regular<Container>;
  requires std::same_as<typename Container::value_type, T>;
  {
    a.cbegin()
    } -> std::same_as<typename Container::const_iterator>;
  {
    a.empty()
    } -> std::same_as<bool>;
};
  1. A HasTicketPaths concept
/*
  `HasTicketPaths` ensures that that structure given implements a `getTicketPaths`
  function that returns a container that satisfies `ContainerOf` with
  `std::filesystem::path` elements in it.
 */
template <typename T>
concept HasTicketPaths = requires(T t)
{
  {
    t.getTicketPaths()
    } -> ContainerOf<fs::path>;
};
  1. A IsStatus concept
/*
  To set status the obj needs at least two elements:
  1. Status string and
  2. the date the status was posted,
  `IsStatus` ensure those constrients by requiring `status` and
  `statusDate` functions that return a `std::string` and `std::time_t`
  respectively.
*/
template <typename T>
concept IsStatus = requires(T t)
{
  {
    t.status()
    } -> std::same_as<std::string>;
  {
    t.statusDate()
    } -> std::same_as<std::time_t>;
};

Now I think all have to do is somehow combine those two concepts in HasStatus concept and change the function prototype to

template <typename T>
requires HasStatus<T> && requires HasTicketPaths<T>
void setFoo(const T& t);

but I am not sure how to do that.

I imagined it'd look something like this

/*
  `HasStatus` ensures that the container has a `getStatus` function that return
  a container with each element type ensuring `IsStatus`'s requirement.
 */
template <typename T>
concept HasStatus = requires(T t)
{
  {
    t.getStatus()
  } -> ContainerOf<IsStatus>;
};

but it generates the following error

invalid use of ‘decltype(auto) [requires ::IsStatus<<placeholder>, >]’ in template argument
   70 |   } -> ContainerOf<IsStatus>;

I think I misunderstood how concepts actually work but I am not sure where/what to look for.

atis
  • 881
  • 5
  • 22

2 Answers2

2

A concept is not a type, so it can’t appear as a container element type—neither in the type of an object (this is why you have to use std::vector<std::any> to approximate std::vector<std::copyable>) nor in the type for your concept ContainerOf. Moreover, you can’t use a concept as a template argument, so you can’t have a higher-order ContainerLike concept.

What you can do is make a Container concept that checks only for empty, add the constraint

{ *c.cbegin() } -> IsStatus;

and apply it to t.getStatus() in another concept.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • Just to make sure I understand it correctly, is this the way you suggest it https://godbolt.org/z/oo11o4jsx – atis Jun 14 '21 at 07:41
  • @atis: Looks reasonable—I just wanted, like you ended up doing, to not mess with `HasTicketPaths` to illustrate it. – Davis Herring Jun 14 '21 at 13:51
0

To help others that may stumble upon this question (or future me), following is a minimal complete/reproducible example for the way I ended up implementing that

#include <concepts>
#include <ctime>
#include <iostream>
#include <string>
#include <vector>

template <typename T>
concept IsStatus = requires(T t) {
  { t.getStatus() } -> std::same_as<const std::string&>;
  { t.getStatusDate() } -> std::same_as<const std::time_t&>;
};

template <class Container>
concept ContainerOfStatus = requires(Container c) {
  { c.empty() } -> std::same_as<bool>;
  { *c.cbegin() } -> IsStatus;
};

template <typename T>
concept HasStatus = requires(T t) {
  { t.getAllStatus() } -> ContainerOfStatus;
};

struct Status {
  std::string status;
  std::time_t statusDate;

  const std::string& getStatus() const { return status; };
  const std::time_t& getStatusDate() const { return statusDate; };
};

struct CurrentStatusStack {
  std::vector<Status> statusVec;

  const std::vector<Status>& getAllStatus() const { return statusVec; };
};

template <typename T>
requires HasStatus<T>
void printStatus(T t) {
  std::cout << t.getAllStatus().cbegin()->getStatus() << std::endl;
}

int main() {
  Status s{"some status", std::time_t(nullptr)};

  CurrentStatusStack st;

  st.statusVec.push_back(s);

  std::cout << st.getAllStatus().cbegin()->getStatus() << std::endl;

  printStatus(st);
}

on godbolt compiler explorer

Here's another very similar question

atis
  • 881
  • 5
  • 22