5

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;
};
Casey
  • 41,449
  • 7
  • 95
  • 125
Amir Kirsh
  • 12,564
  • 41
  • 74

1 Answers1

10

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!
}
Amir Kirsh
  • 12,564
  • 41
  • 74
  • 1
    Using macros to create concepts? :( – Yehezkel B. Feb 27 '20 at 13:43
  • 1
    Yeah, I understand the need here, just not sure it really worth it. It's not that hard to write the concept directly. – Yehezkel B. Feb 27 '20 at 13:45
  • Anyway, great example for partial ordering, when it works and when it doesn't. Thanks! (Maybe shouldn't be hidden in an answer for a different aspect of concepts) – Yehezkel B. Feb 27 '20 at 13:45
  • @YehezkelB. I will be happy if you would drop a question on partial ordering, I may try to answer it with a more simple example. This is for sure a subject that would have much confusion and misunderstandings – Amir Kirsh Feb 27 '20 at 13:49
  • Worth noting is that changing the definition of the concept `has_fields_foo_and_moo` from `has_field_foo

    && has_field_moo

    ;` to `requires { P::foo; P::moo; };` will still lead to the same ambiguity issue.

    – 303 Sep 12 '22 at 08:45