为什么for循环不是编译时表达式? [英] Why isn't a for-loop a compile-time expression?

查看:80
本文介绍了为什么for循环不是编译时表达式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果我想对元组进行迭代,则必须使用疯狂的模板元编程和模板助手专业化。例如,以下程序将不起作用:

If I want to do something like iterate over a tuple, I have to resort to crazy template metaprogramming and template helper specializations. For example, the following program won't work:

#include <iostream>
#include <tuple>
#include <utility>

constexpr auto multiple_return_values()
{
    return std::make_tuple(3, 3.14, "pi");
}

template <typename T>
constexpr void foo(T t)
{
    for (auto i = 0u; i < std::tuple_size<T>::value; ++i)
    {
        std::get<i>(t);
    }    
}

int main()
{
    constexpr auto ret = multiple_return_values();
    foo(ret);
}

因为 i 可以不能是 const ,否则我们将无法实现它。但是for循环是可以静态评估的编译时构造。借助as-if规则,编译器可以自由地删除,转换,折叠,展开或执行任何操作。但是,为什么不能以constexpr方式使用循环呢?此代码中没有任何事情需要在运行时完成。

Because i can't be const or we wouldn't be able to implement it. But for loops are a compile-time construct that can be evaluated statically. Compilers are free to remove it, transform it, fold it, unroll it or do whatever they want with it thanks to the as-if rule. But then why can't loops be used in a constexpr manner? There's nothing in this code that needs to be done at "runtime". Compiler optimizations are proof of that.

我知道您可以在循环体内修改 i ,但是编译器仍然可以检测到该错误。示例:

I know that you could potentially modify i inside the body of the loop, but the compiler can still be able to detect that. Example:

// ...snip...

template <typename T>
constexpr int foo(T t)
{
    /* Dead code */
    for (auto i = 0u; i < std::tuple_size<T>::value; ++i)
    {
    }    
    return 42;
}

int main()
{
    constexpr auto ret = multiple_return_values();
    /* No error */
    std::array<int, foo(ret)> arr;
}

由于 std :: get<>() 是一个编译时构造,与 std :: cout.operator<< 不同,我不明白为什么不允许这样做。

Since std::get<>() is a compile-time construct, unlike std::cout.operator<<, I can't see why it's disallowed.

推荐答案

πάνταῥεῖ给出了一个很好而有用的答案,尽管 constexpr for

πάντα ῥεῖ gave a good and useful answer, I would like to mention another issue though with constexpr for.

在C ++中,在最基本的级别上,所有表达式的类型都可以静态确定(在编译时)。当然有类似RTTI和 boost :: any 之类的东西,但是它们是基于此框架构建的,表达式的静态类型是理解某些内容的重要概念。

In C++, at the most fundamental level, all expressions have a type which can be determined statically (at compile-time). There are things like RTTI and boost::any of course, but they are built on top of this framework, and the static type of an expression is an important concept for understanding some of the rules in the standard.

假设您可以使用特殊的语法在异类容器上进行迭代,例如:

Suppose that you can iterate over a heterogenous container using a fancy for syntax, like this maybe:

std::tuple<int, float, std::string> my_tuple;
for (const auto & x : my_tuple) {
  f(x);
}

此处, f 是一些重载的功能。显然,此方法的意图是为元组中的每种类型调用不同的 f 重载。这实际上意味着在表达式 f(x)中,重载解析必须运行三个不同的时间。如果我们按照C ++的当前规则进行操作,那么唯一有意义的方法是将循环基本展开为三个不同的循环体,在之前中,我们尝试找出表达式的类型

Here, f is some overloaded function. Clearly, the intended meaning of this is to call different overloads of f for each of the types in the tuple. What this really means is that in the expression f(x), overload resolution has to run three different times. If we play by the current rules of C++, the only way this can make sense is if we basically unroll the loop into three different loop bodies, before we try to figure out what the types of the expressions are.

如果代码实际上是

for (const auto & x : my_tuple) {
  auto y = f(x);
}

auto 不是魔术,这并不意味着没有类型信息,而是请推断出类型,请编译器。但显然,通常确实需要三种不同的 y

auto is not magic, it doesn't mean "no type info", it means, "deduce the type, please, compiler". But clearly, there really need to be three different types of y in general.

这种事情是棘手的问题-在C ++中,解析器需要能够知道什么名称是类型,什么名称是模板才能正确地解析语言。解析器是否可以修改为在解析所有类型之前对 constexpr c循环进行一些循环展开?我不知道,但我认为这可能并不重要。也许有更好的方法...

On the other hand, there are tricky issues with this kind of thing -- in C++ the parser needs to be able to know what names are types and what names are templates in order to correctly parse the language. Can the parser be modified to do some loop unrolling of constexpr for loops before all the types are resolved? I don't know but I think it might be nontrivial. Maybe there is a better way...

为避免此问题,在当前版本的C ++中,人们使用了访问者模式。这个想法是,您将有一个重载的函数或函数对象,并将其应用于序列中的每个元素。然后,每个重载都有其自己的主体,因此它们中变量的类型或含义没有任何歧义。有像 boost :: fusion boost :: hana 之类的库,您可以使用给定的序列对异构序列进行迭代vistior-您可以使用它们的机制而不是for循环。

To avoid this issue, in current versions of C++, people use the visitor pattern. The idea is that you will have an overloaded function or function object and it will be applied to each element of the sequence. Then each overload has its own "body" so there's no ambiguity as to the types or meanings of the variables in them. There are libraries like boost::fusion or boost::hana that let you do iteration over heterogenous sequences using a given vistior -- you would use their mechanism instead of a for-loop.

如果可以使用 constexpr for 只是整数,例如

If you could do constexpr for with just ints, e.g.

for (constexpr i = 0; i < 10; ++i) { ... }

这带来了与异构for循环相同的难度。如果可以在主体内部使用 i 作为模板参数,则可以在循环主体的不同运行中创建引用不同类型的变量,然后不清楚是什么

this raises the same difficulty as heterogenous for loop. If you can use i as a template parameter inside the body, then you can make variables that refer to different types in different runs of the loop body, and then it's not clear what the static types of the expressions should be.

因此,我不确定,但是我认为实际添加<$ c可能会涉及一些非同寻常的技术问题$ c> constexpr for 功能的语言。访客模式/计划的反射功能最终可能会减轻IMO的负担...谁知道。

So, I'm not sure, but I think there may be some nontrivial technical issues associated with actually adding a constexpr for feature to the language. The visitor pattern / the planned reflection features may end up being less of a headache IMO... who knows.

我再举一个我刚刚想到的例子,显示了所涉及的困难。

Let me give another example I just thought of that shows the difficulty involved.

在普通的C ++中,编译器知道堆栈上每个变量的静态类型,因此它可以计算该函数的堆栈框架布局。

In normal C++, the compiler knows the static type of every variable on the stack, and so it can compute the layout of the stack frame for that function.

您可以确定函数执行时局部变量的地址不会改变。例如,

You can be sure that the address of a local variable won't change while the function is executing. For instance,

std::array<int, 3> a{{1,2,3}};
for (int i = 0; i < 3; ++i) {
    auto x = a[i];
    int y = 15;
    std::cout << &y << std::endl;
}

在此代码中, y 是for循环主体中的局部变量。它在整个函数中都有一个定义明确的地址,并且每次编译器打印的地址都相同。

In this code, y is a local variable in the body of a for loop. It has a well-defined address throughout this function, and the address printed by the compiler will be the same each time.

constexpr中类似代码的行为应该是什么?

What should be the behavior of similar code with constexpr for?

std::tuple<int, long double, std::string> a{};
for (int i = 0; i < 3; ++i) {
    auto x = std::get<i>(a);
    int y = 15;
    std::cout << &y << std::endl;
}

重点是 x的类型在每次循环遍历中的推导方法不同-由于它的类型不同,因此在堆栈上的大小和对齐方式可能不同。由于 y 紧随其后,因此这意味着 y 可能会在循环的不同运行中更改其地址- -对吗?

The point is that the type of x is deduced differently in each pass through the loop -- since it has a different type, it may have different size and alignment on the stack. Since y comes after it on the stack, that means that y might change its address on different runs of the loop -- right?

如果在循环中一遍获得指向 y 的指针应该怎么做,然后在以后的通行证中取消引用?即使在上面显示了 std :: array 的类似 no-constexpr for代码中这可能是合法的,也应该是未定义的行为吗?

What should be the behavior if a pointer to y is taken in one pass through the loop, and then dereferenced in a later pass? Should it be undefined behavior, even though it would probably be legal in the similar "no-constexpr for" code with std::array showed above?

y 的地址应该不允许更改吗?编译器是否应该填充 y 的地址,以便可以在 y ?这是否意味着编译器不能简单地展开循环并开始生成代码,而是必须事先展开循环的每个实例,然后从每个 N 收集所有类型信息。 code>实例化然后找到满意的布局?

Should the address of y not be allowed to change? Should the compiler have to pad the address of y so that the largest of the types in the tuple can be accommodated before y? Does that mean that the compiler can't simply unroll the loops and start generating code, but must unroll every instance of the loop before-hand, then collect all of the type information from each of the N instantiations and then find a satisfactory layout?

我认为您最好只使用pack扩展,这显然应该如何做由编译器实现,以及在编译和运行时效率如何。

I think you are better off just using a pack expansion, it's a lot more clear how it is supposed to be implemented by the compiler, and how efficient it's going to be at compile and run time.

这篇关于为什么for循环不是编译时表达式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆