cs-
cs-

Reputation: 485

Formalizing a concept with conditional requirements

A benefit of concepts in C++20 is that this feature allows, and encourages, programmers to specify (using the C++ language itself) information about their interfaces that formerly had to be in human-only documentation, in a natural language like English.

For example, I have a generic algorithm, let's call it template<int N, template<int> class X> void foo(X<N>). My foo() solves a certain problem in the numerical domain. My plain-English documentation for how to use foo() says something like this: "foo() accepts an argument of class X<N>, which the user of foo must implement. The class X<N> has an integer template parameter N describing how many rows it has. Class X<N> provides operator [] to access the elements of a row. X<N> also provides a member function reduce(), unless N=1, in which case reduction does not make sense because there is only one row."

How would I conceptify this? My first approach is just:

template<class T>
concept Fooable = requires(T x, int i) {
  x[i];
}

But this does not formalize the reduce() requirement.

If I have only one (formal) concept, then I cannot include x.reduce() in the requires expression because some Fooable classes, namely those that have N=1, do not and cannot implement the reduce() method.

I would like my requires expression to include something like if constepxr(T::N > 1) x.reduce(); but if is a control-flow statement not an expression, so cannot be in the requires expression.

Question: How can I formalize this contract using C++20 concepts?

Upvotes: 6

Views: 1021

Answers (1)

KamilCuk
KamilCuk

Reputation: 140990

Well, that was strangely easy.

#include <concepts>
#include <cstddef>
#include <type_traits>

template<int N, template<int> class X>
concept Fooable =
requires(X<N> a, int i) { a[i]; } &&
(
    N == 1 ||
    requires(X<N> a) { a.reduce(); }
);

template<int N, template<int> class X>
requires Fooable<N, X>
void foo(X<N>) {}

template<int N>
struct Myx1 {
    int operator[](int) { return 0; };
};

template<int N>
struct Myx2 {
    int operator[](int) { return 0; }
    int reduce() { return 0; }
};

int main() {
    foo(Myx1<1>{});
    foo(Myx1<2>{}); // error - no reduce() and N != 1
    foo(Myx2<2>{});
}

The || operator in concepts is short-circuiting, just like normal operator, so N == 1 || something works as expected.

Upvotes: 4

Related Questions