Lets say we have a simple concept like:
template <typename T>
concept MyConcept = requires {
T::value == 42;
};
In my understanding the concept says that, if the code T::value == 42
is valid for the type T, I pass. So the value
MUST be a static member, right?
I have a struct
struct Test { int value = 0; }
and the next template function
template <MyConcept T>
void call() { /*...*/ }
and when I try to do this:
int main()
{
call<Test>();
}
It works!
And the question is: why does it work? Test::value == 42
is not a valid code for the type Test
.
I found a method to fix it like:
template <typename T>
concept MyConcept = requires {
*(&T::value) == 42;
};
And it "works" as expected:
<source>:6:20: note: the required expression '((* & T::value) == 42)' is invalid
And this concept works for the static value
only, as it should be.
But why does the T::value == 42
work?
godbolt: https://godbolt.org/z/d3GPETEq9
UPD: example https://godbolt.org/z/d8qfzK9b6
CodePudding user response:
And this concept works for the static value only, as it should be. But why does the
T::value == 42
work?
Because there's actually an exception in the rule you think will cause this to fail.
The rule, from [expr.prim.id.general]/3 is, emphasis mine:
An id-expression that denotes a non-static data member or non-static member function of a class can only be used:
- as part of a class member access in which the object expression refers to the member's class51 or a class derived from that class, or
- to form a pointer to member ([expr.unary.op]), or
- if that id-expression denotes a non-static data member and it appears in an unevaluated operand.
[Example 3:
struct S { int m; }; int i = sizeof(S::m); // OK int j = sizeof(S::m 42); // OK
— end example]
That third bullet right there: T::value
is usable as an unevaluated operand. All the expressions you check in a requirement are unevaluated. So T::value
works for non-static data members just fine.