Search code examples
c++language-lawyerc++20c++-concepts

static assertion fires from return type when leading constraint is not satisfied


GCC accepts this code, Clang and MSVC reject it due to failed static assertion in assert. What does the standard say?

https://godbolt.org/z/PKMKzYGsc

template<typename T>
constexpr int assert() {
    static_assert(sizeof(T) == 1);
    return 1;
}

template<auto I>
using Int = int;

template<typename T> requires (sizeof(T) == 1)
constexpr auto foo(T) -> Int<assert<T>()> {
    return 1;
}

template<typename T> requires (sizeof(T) > 1)
constexpr auto foo(T a) -> Int<1> {
    return 2;
}

static_assert(foo('1') == 1);
static_assert(foo(2) == 2);

Clang output:

<source>:3:19: error: static assertion failed due to requirement 'sizeof(int) == 1'
    3 |     static_assert(sizeof(T) == 1);
      |                   ^~~~~~~~~~~~~~
<source>:11:30: note: in instantiation of function template specialization 'assert<int>' requested here
   11 | constexpr auto foo(T) -> Int<assert<T>()> {
      |                              ^
<source>:21:15: note: while substituting deduced template arguments into function template 'foo' [with T = int]
   21 | static_assert(foo(2) == 2);
      |               ^
<source>:3:29: note: expression evaluates to '4 == 1'
    3 |     static_assert(sizeof(T) == 1);

Solution

  • During template argument deduction, the satisfaction check of the function template's associated constraints takes place before the substitution of template arguments into the function type. This is described in [temp.deduct.general]/5:

    If the function template has associated constraints, those constraints are checked for satisfaction. If the constraints are not satisfied, type deduction fails. [...] If type deduction has not yet failed, then all uses of template parameters in the function type are replaced with the corresponding deduced or default argument values.

    This means that type deduction should fail before encountering the problematic assert in this example, making it well-formed.

    This outcome is the result of CWG2369, prior to which the two steps above happened in the opposite order. Evidently, Clang and MSVC don't implement this change yet, leading to the observed implementation divergence.