Using concepts for checking if a type T has a field F

Viewed 497

The following concept checks if a type T has a public field foo:

template<typename T>               
concept has_field_foo = requires { 
    T::foo;                       
};

Is there a way to implement a generic concept that would check if a type T has a public field F, something like (pseudo code... the field F cannot be passed like this):

template<typename T, typename F>               
concept has_field = requires { 
    T::F;
};
1 Answers

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!
}
Related