Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 592
Vittorio Romeo Avatar asked Nov 08 '22 07:11

Vittorio Romeo


1 Answers

The inconsistent behavior was reported as bug #78131.

like image 59
Vittorio Romeo Avatar answered Nov 14 '22 22:11

Vittorio Romeo