c++c++11constexprconstexpr-function

Can't use function parameter of a constexpr function in a constant expression


Consider the following code:

static constexpr int make_const(const int i){
    return i;
}

void t1(const int i)
{
    constexpr int ii = make_const(i);  // error occurs here (i is not a constant expression)
    std::cout<<ii;
}

int main()
{
   t1(12);
}

Why I have an error on make_const call?


UPDATE

But this one works:

constexpr int t1(const int i)
{
    return make_const(i);
}

However, this not:

template<int i>
constexpr bool do_something(){
    return i;
}

constexpr int t1(const int i)
{
    return do_something<make_const(i)>();   // error occurs here (i is not a constant expression)
}

Solution

  • A constexpr function and a constexpr variable are related, but different things.

    A constexpr variable is a variable whose value is guaranteed to be available at compile time.

    A constexpr function is a function that, if evaluated with constexpr arguments, and behaves "properly" during its execution, will be evaluated at compile time.

    If you pass a non-constexpr int to a constexpr function, it will not magically make it evaluated at compile time. It will, however, be allowed to pass the constexprness of its input parameters through itself (normal functions cannot do this).

    constexpr on functions is a mixture of documentation and restriction on how they are written and instructions to the compiler.

    The reason behind this is to allow the same function to be evaluated both at compile time, and at run time. If passed runtime arguments, it is a runtime function. If passed constexpr arguments, it may be evaluated at compile time (and will be if used in certain contexts).

    Note that consteval may be what you are looking for for a function. But maybe not.

    You are getting errors because by passing in runtime values, you cannot get a compile time value out.

    There are ways around this. My favorite is a std::variant of std::integer_constant; you can pick which is active at runtime, then std::visit to get the compile time constant. The downside is that this can generate a lot of code really easily.

    template<auto I>
    using constant_t=std::integral_constant<decltype(I),I>;
    template<auto I>
    constexpr constant_t<I> constant_v={};
    template<auto...Is>
    using var_enum_t=std::variant<constant_t<Is>...>;
    template<class Indexes>
    struct var_enum_over;
    template<class Indexes>
    using var_enum_over_t=typename var_enum_over<Indexes>::type;
    template<class T,T...ts>
    struct var_enum_over<std::integral_sequence<T,Is...>>{
      using type=var_enum_t<Is...>;
    };
    template<std::size_t N>
    using var_index_t=var_enum_over_t<std::make_index_sequence<N>>;
    
    template<std::size_t N>
    var_index_t<N> var_index(std::size_t I){
      constexpr auto table=[]<std::size_t...Is>(std::index_sequence<Is...>)->std::array<N,var_index_t<N>>{
        return { var_index_t<N>(constant_v<Is>)..., };
      }(std::make_index_sequence<N>{});
      if (I>=N) throw 0; // todo: something better
      return table[I];
    }
    

    (Probably has typos).

    Now you can:

    auto idx=var_index<5>(3/* 3 can be runtime */);
    std::visit([](auto three){
      // three is a compile time value here
    }, idx);