简体   繁体   English

constexpr函数中的编译时类型生成

[英]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: 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'

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. 如果我们假设函数(和相应的循环)是在编译时评估的(这对于从c ++ 14开始的循环是可行的),那么为什么即使未声明为,该代码也无法编译得那么远。 const ,实际上它可以是constexpr,因为它的所有值在编译时也是已知的。

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 . 其中j是一个常量表达式的占位符。 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) . 请注意,较高级别的抽象超过for_each_arg可以很容易地提供(例如迭代数的编译时间范围,或一个转换constexpr阵列的序列integral_constant和遍历该代替)。

The compiler is right. 编译器是正确的。 i and j and not constexpr. ij而不是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. 如果您尝试标记j constexpr,您会发现由于i不在,所以事实并非如此。

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. 如果你试图声明i constexpr,你会看到,constexpr变量都受到相同的规则为任何constexpr变量:你不能变异他们。

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函数都必须能够在运行时进行评估。

constexpr does not mean "must run at compile time", it means "could possibly be run at compile time". constexpr并不表示“必须在编译时运行”,而是表示“可以在编译时运行”。

There is no fundamantal reason why you could not have a constexpr for loop that made the index a constexpr value on each iteration. 没有根本原因,为什么没有constexpr for循环使索引在每次迭代时成为constexpr值。 But C++ does not have that. 但是C ++没有。

It does have a constexpr if which is similar in spirit to what you want. 它的确具有constexpr if它在本质上与您想要的东西相似。

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). 是C ++ 17中未编译的示例(主要是17,因为它具有constexpr lambdas)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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