Генерация типов во время компиляции в функциях constexpr

#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;
    }
}

Код не может быть скомпилирован с помощью gcc-7.2 с --std = c ++ 17 со следующими ошибка компиляции:

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

Если мы предположим, что функция (и соответствующий цикл) оценивается во время компиляции (что применимо для циклов, начиная с c ++ 14), почему тогда этот код не может быть скомпилирован, поскольку, хотя i не объявлен как const, на самом деле он может быть constexpr, поскольку все его значения также известны во время компиляции.

Не могли бы вы пояснить, недействителен ли этот код по самой его идее? Или есть ограничение компилятора? Или ничего из следующего?


person Alexandra B.    schedule 01.09.2017    source источник


Ответы (3)


Не могли бы вы пояснить, недействителен ли этот код по самой его идее?

Это - вы пытаетесь использовать изменяемую итерационную переменную с отслеживанием состояния в качестве константного выражения. Вся концепция постоянного выражения вращается вокруг неизменности. Не имеет значения, выполняется ли цикл во время компиляции.

На самом деле вам нужно сгенерировать код для следующего фрагмента:

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

Где j - это заполнитель для постоянного выражения. Вот возможный способ сделать это:

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>{});
}

живой пример на wandbox

Обратите внимание, что более высокоуровневые абстракции над for_each_arg могут быть легко предоставлены (например, перебирать диапазон чисел во время компиляции или преобразовывать массив constexpr в последовательность integral_constant и вместо этого перебирать его).

person Vittorio Romeo    schedule 01.09.2017

Компилятор прав. i и j, а не constexpr. Посмотрите сами:

//    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;
    }

Если вы попытаетесь пометить j constexpr, вы увидите, что, поскольку i нет, это не может быть так.

Если вы попытаетесь объявить i constexpr, вы увидите, что переменные constexpr подчиняются тому же правилу, что и любая переменная constexpr: вы не можете их изменять.

Итак, как вы можете перебирать числа для генерации типов?

Вы можете использовать расширение пакета с последовательностями индексов:

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;
    });
}
person Guillaume Racicot    schedule 01.09.2017

Каждая constexpr функция должна иметь возможность оцениваться во время выполнения.

constexpr не означает «должен выполняться во время компиляции», это означает «возможно, может быть запущен во время компиляции».

Нет фундаментальной причины, по которой у вас не могло бы быть constexpr for цикла, который делал бы индекс constexpr значением на каждой итерации. Но в C ++ этого нет.

У него есть constexpr if, который по духу похож на то, что вы хотите.

Пока это не получится, вы должны написать свой собственный.

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;
  });
}

- это некомпилированный exampke в C ++ 17 (в основном 17, потому что у него есть лямбда-выражения constexpr).

person Yakk - Adam Nevraumont    schedule 01.09.2017