Behavior of concepts with a default template parameter
Asked Answered
W

1

5

Consider this concept, which has a default template parameter.

template<class T, class = decltype([]{})>
concept IsDefined = sizeof(T) > 0;

Since every lambda has a distinct type, one might expect every instantiation of IsDefined<X> to be distinct.

struct SomeType;

static_assert( false == IsDefined<SomeType> );

struct SomeType
{
   // Defined.
};

static_assert( true == IsDefined<SomeType> );

Clang and MSVC agree and compile this code. GCC fails the second static assert, but only if the first static assert is evaluated.

error: static assertion failed
   17 | static_assert( true == IsDefined<SomeType> );

Are any of these compilers wrong? Or is this behavior unspecified?

See compiler results here

Weiser answered 19/7 at 16:7 Comment(7)
If a lambda is declared inside a default argument, is it different for each call site?Nigrosine
That duplicate seems to be asking about function parameters. And this issue is specific to concepts.Weiser
There was no need to reopen this question. The discussion there is perfectly applicable here also.Nigrosine
@Nigrosine to be clear, I am asking why concepts behave differently from the behaviors and answers cited in your link. I believe that is a fair question. Are any of these compilers wrong?Weiser
class = decltype([]{}) is not a default functional argument. Why do you expect it to be distinct at concept instantiations? It may be not specified by the standard.Gaven
@Gaven I agree. Interestingly, the three major compilers agree that such a template parameter is unique in other contexts. Perhaps "not specified" is the answer.Weiser
I'm just seeing ODR violations.Ihram
T
11

Concepts are not instantiated. Instead, the expression is normalized into a constraint, and then when a concept-id is evaluated, the constraint is checked for satisfaction.

In this case the normal form of IsDefined is the atomic constraint sizeof(T) > 0 with the identity mapping T -> T; note that the second parameter does not appear in the mapping (see [temp.constr.atomic]/1). Your code is IFNDR by [temp.constr.atomic]/3:

If, at different points in the program, the satisfaction result is different for identical atomic constraints and template arguments, the program is ill-formed, no diagnostic required.

Tocharian answered 20/7 at 6:34 Comment(1)
So "template<typename T> struct dependent_zero : std::integral_constant<std::size_t, 0zu> {}; template<class T, class Unique = decltype([]{})> concept IsDefined = sizeof(T) >= dependent_zero<Unique>::value;" would not be IFNDR because the atomic constraints are not identical?Coparcenary

© 2022 - 2024 — McMap. All rights reserved.