Vittorio Romeo Vittorio Romeo - 1 month ago 11
C++ Question

Inconsistent evaluation for `constexpr` lambdas in templates between `static_assert`, `if constexpr(...)` and `constexpr` variables

(Using

g++ 7.0
trunk.)

Given the following "type-to-value wrapping" utilities...

template <typename T>
struct type_wrapper { using type = T; };

// "Wraps" a type into a `constexpr` value.
template <typename T>
constexpr type_wrapper<T> type_c{};


...I created the following function that checks the validity of an expression:

template <typename TF>
constexpr auto is_valid(TF)
{
return [](auto... ts) constexpr
{
return std::is_callable<TF(typename decltype(ts)::type...)>{};
};
}





The
is_valid
function can be used as follows:

// Evaluates to `true` if `some_A.hello()` is a valid expression.
constexpr auto can_add_int_and_float =
is_valid([](auto _0) constexpr -> decltype(_0.hello()){})
(type_c<A>);

// Evaluates to `true` if `some_int + some_float` is a valid expression.
constexpr auto can_add_int_and_float =
is_valid([](auto _0, auto _1) constexpr -> decltype(_0 + _1){})
(type_c<int>, type_c<float>);


It can also be used inside
static_assert
...

static_assert(is_valid([](auto _0) constexpr -> decltype(_0.hello()){})
(type_c<A>));


...and inside
if constexpr
:

if constexpr(
is_valid([](auto _0) constexpr -> decltype(_0.hello()){})
(type_c<A>)) { /* ... */ }





However, when
is_valid
is used inside a template function (passing the template parameters as
type_c
values)
, something weird happens:


  • static_assert(is_valid(/*...*/))
    works properly.

  • constexpr auto x = is_valid(/*...*/)
    works properly.

  • if constexpr(is_valid(/*...*/)
    fails to compile.





// Compiles and works as intended.
template <typename T0, typename T1>
void sum_ok_0(T0, T1)
{
static_assert(
is_valid([](auto _0, auto _1) constexpr
-> decltype(_0 + _1){})(type_c<T0>, type_c<T1>)
);
}

// Compiles and works as intended.
template <typename T0, typename T1>
void sum_ok_1(T0, T1)
{
constexpr auto can_sum =
is_valid([](auto _0, auto _1) constexpr
-> decltype(_0 + _1){})(type_c<T0>, type_c<T1>);

if constexpr(can_sum) { }
}

// Compile-time error!
template <typename T0, typename T1>
void sum_fail_0(T0, T1)
{
if constexpr(is_valid([](auto _0, auto _1) constexpr
-> decltype(_0 + _1){})(type_c<T0>, type_c<T1>)) { }
}


Error:

In function 'void sum_fail_0(T0, T1)':
64:95: error: expression '<lambda>' is not a constant expression
if constexpr(is_valid([](auto _0, auto _1) constexpr -> decltype(_0 + _1){})(type_c<T0>, type_c<T1>)) { }


Why is this failing to compile only for the
if constexpr(is_valid(/*...*/))
case?
This is inconsistent with
static_assert
and
constexpr auto x = /*...*/
.

Is this a defect in
g++
's implementation of
if constexpr
?

Full example on wandbox.

Answer

The inconsistent behavior was reported as bug #78131.

Comments