Reputation: 485
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
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