Checking if a parameter provided has a field F can be easily achieved with a requires
constraint on the function itself:
// accepts only parameters that have a 'foo' member
void doSomething(auto i) requires requires { i.foo; } { /* */ }
For why (and when) C++20 requires requires
requires
see:
Why do we require requires requires?
Above method can perfectly live together with the generic case overload:
// the unconstrained version
void doSomething(auto i) { /* */ }
the correct method would be picked according to the parameter provided.
Code: https://godbolt.org/z/u35Jo3
To have a generic concept, we may drop in a macro to help us:
#define CREATE_HAS_FIELD_CONCEPT(field) \
template<typename T> \
concept has_field_##field = requires { \
T::field; \
}
We actually don't have a generic concept, but we can easily generate the required concept with above macro:
CREATE_HAS_FIELD_CONCEPT(foo); // creates the concept: has_field_foo
And use it (instead of the version with the requires above):
void doSomething(has_field_foo auto i) { /* */ }
Code: https://godbolt.org/z/R9nQ7Q
There is some value in actually creating a concept, as it can participate in partial ordering.
With a plain constraint we do not get partial ordering, as atomic constraints aren't considered equivalent, but atomic concepts are.
So the following code, based on plain constraint, fails with ambiguity:
void print(auto i) requires requires { i.foo; } {
std::cout << "foo" << std::endl;
}
void print(auto i) requires requires { i.moo; } {
std::cout << "moo" << std::endl;
}
void print(auto i) requires requires { i.moo && i.foo; } {
std::cout << "foo and moo" << std::endl;
}
struct HasFoo { int foo; };
struct HasMoo { int moo; };
struct HasFooAndMoo: HasFoo, HasMoo {};
int main() {
print(HasFoo{});
print(HasMoo{});
print(HasFooAndMoo{}); // compilation error: ambiguity
// all 3 'print' functions are proper candidates
// no partial ordering for constraints, just for concepts!
}
while this one works as desired:
CREATE_HAS_FIELD_CONCEPT(foo); // creates the concept: has_field_foo
CREATE_HAS_FIELD_CONCEPT(moo); // creates the concept: has_field_moo
void print(has_field_foo auto i) {
std::cout << "foo" << std::endl;
}
void print(has_field_moo auto i) {
std::cout << "moo" << std::endl;
}
template<class P>
concept has_fields_foo_and_moo
= has_field_foo<P> && has_field_moo<P>;
void print(has_fields_foo_and_moo auto i) {
std::cout << "foo and moo" << std::endl;
}
int main() {
print(HasFoo{});
print(HasMoo{});
print(HasFooAndMoo{}); // partial ordering for concepts rocks!
}