简体   繁体   中英

Compile-time types generation in constexpr functions

#include <array>
#include <tuple>

typedef std::tuple<const int> TupleType;

constexpr std::array<const int, 2> a = {1, 2};

constexpr void foo()
{
    for (std::size_t i = 0; i < a.size(); ++i)
    {
        const int j = i;
        typedef std::tuple_element<j, TupleType> T;
    }
}

The code can not be compiled by gcc-7.2 with --std=c++17 with the following compilation error:

error: the value of 'j' is not usable in a constant expression
note: in template argument for type 'long unsigned int'

If we assume that function (and the corresponding loop) is evaluated in compile-time (which is viable for loops starting from c++14 ), why then this code can not be compiled as far as, even though i is not declared as const , it can actually be constexpr as all of its values are known at compile-time as well.

Could you please clarify whether this code is invalid by its very idea? Or there is a compiler limitation? Or none of the following?

Could you please clarify whether this code is invalid by its very idea?

It is - you are trying to use a mutable and stateful iteration variable as a constant expression . The whole concept of a constant expression revolves around immutability. It doesn't matter if the loop is executed during compilation.

What you actually should do here is generate code for the following snippet:

{
    typedef std::tuple_element<j, TupleType> T;
    // ...
}

Where j is a placeholder for a constant expression . Here's a possible way of doing it:

template <typename F, typename... Ts>
constexpr void for_each_arg(F&& f, Ts&&... xs)
{
    (f(std::forward<Ts>(xs)), ...);
}

constexpr void foo()
{
    for_each_arg([](auto c)
    {
        typedef std::tuple_element<c, TupleType> T;
    },
    std::integral_constant<int, 1>{}, 
    std::integral_constant<int, 2>{});
}

live example on wandbox

Note that higher-level abstractions over for_each_arg could be easily provided (eg iterate over a compile-time range of numbers, or convert a constexpr array to a sequence of integral_constant and iterate over that instead) .

The compiler is right. i and j and not constexpr. Look for yourself:

//    v--- not constexpr
for (std::size_t i = 0; i < a.size(); ++i)
    {
        // Not constexpr either
        const int j = i;
        typedef std::tuple_element<j, TupleType> T;
    }

If you try to mark j constexpr, you'd see that since i is not, it cannot be so.

If you try to declare i constexpr, you will see that constexpr variables are subject to the same rule as any constexpr variable: you cannot mutate them.

So how can you loop over numbers to generate the types?

You can use pack expansion with index sequences:

template<typename T, T... S, typename F>
void for_sequence(std::integer_sequence<S...>, F f)
{
    using unpack = int[];
    (void) unpack{(f(std::integral_constant<T, S>{}), void(), 0)..., 0};
}

constexpr void foo()
{
    for_sequence(std::make_index_sequence<a.size()>{}, [](auto i)
    {
        typedef std::tuple_element<i, TupleType> T;
    });
}

Every constexpr function must be able to be evaluated at runtime.

constexpr does not mean "must run at compile time", it means "could possibly be run at compile time".

There is no fundamantal reason why you could not have a constexpr for loop that made the index a constexpr value on each iteration. But C++ does not have that.

It does have a constexpr if which is similar in spirit to what you want.

Until it gets that, you have to write your own.

template<std::size_t...Is>
constexpr auto index_over( std::index_sequence<Is...> ){
  return [](auto&& f){
    return decltype(f)(f)( std::integral_constant<std::size_t, Is >{}... );
  };
}
template<std::size_t N>
constexpr auto index_upto( std::integral_constant<std::size_t,N> ={} ){
  return index_over( std::make_index_sequence<N>{} );
}
template<class F>
constexpr auto foreacher(F&&f){
  return [f](auto&&...args){
    ( (void)(f(decltype(args)(args)), ... );
  };
}

constexpr void foo()
{
  index_upto<a.size()>()(foreacher([](auto I){
    typedef std::tuple_element<I, TupleType> T;
  });
}

is an uncompiled exampke in C++17 (mostly 17 because it has constexpr lambdas).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM