Home > database >  Concept checks with invalid function calls succeed
Concept checks with invalid function calls succeed

Time:10-21

When defining c 20 concepts to check that requirements are fulfilled by calling a constrained function the behavior is different in g and clang. g accept a type if the checking function is invalid, clang does the opposite:

// constrained function
template <class T>
constexpr void foo1(const T& type)
  requires requires { type.foo(); }{} 

// function with required expression in the body
template <class T>
constexpr auto foo2(const T& type)
  { type.foo(); }                       // (x)

// check for valid expression foo1
template <class T>
concept checkFoo1 = requires(T t) { foo1(t); };

// check for valid expression foo2
template <class T>
concept checkFoo2 = requires(T t) { foo2(t); };

Checking the concept for a type that does not have foo() as member gives unconsistent behavior:

struct Test 
{
  // void foo() const {}
};

int main()
{
  static_assert(checkFoo1<Test>);   // (1)
  static_assert(checkFoo2<Test>);   // (2)
  static_assert(!checkFoo1<Test>);  // (3)
  static_assert(!checkFoo2<Test>);  // (4)
}

In clang-15: (1),(2): static assertion, (3),(4): succeed, additionally an error in (x), see https://godbolt.org/z/zh18rcKz7

g -12: (1),(4): static assertion, (2),(3): succeed, additionally an error in (x), see https://godbolt.org/z/qMsa59nd3

In all cases, the concept check does not tell in the error messages of the static assertion why it failed, i.e., that the member function .foo() is not found. It just tells that the call to foo1() or foo2() is invalid.

My questions are:

  • What is the correct behavior and why?
  • How to check concepts by constrained functions with detailed information about why the call to foo1() or foo2() is invalid and which constraint of these functions is not fulfilled.

Sure, I could check directly for the existence of the member function foo(). But this is just an example. The goal is to emulate something like recursive concepts by recursive function calls to constrained functions. Applications are: checking that all elements of a tuple fulfill some concept, or to check concepts for all nodes in a type tree.

CodePudding user response:

Calling/instantiate foo2 with invalid type would produce hard error, and not a substitution error (to SFINAE out).

So checkFoo2<Test> makes the program ill-formed.

As compilers try to continue to give more errors, they handle the error in some way which are not the same in given case.

Both compilers are right. and then even correctly identify the problem:

error: 'const struct Test' has no member named 'foo'.

but following errors, (coming from the above one) are not necessary relevant/exact.

  • Related