【问题标题】:why for-loop isn't a compile time expression and extended constexpr allows for-loop in a constexpr function为什么 for-loop 不是编译时表达式,而扩展的 constexpr 允许在 constexpr 函数中进行 for-loop
【发布时间】:2017-02-02 14:48:25
【问题描述】:

我写了这样的代码

#include <iostream>
using namespace std;
constexpr int getsum(int to){
    int s = 0;
    for(int i = 0; i < to; i++){
        s += i;
    }
    return s;
}
int main() {
    constexpr int s = getsum(10);
    cout << s << endl;
    return 0;
}

我知道它之所以有效是因为extended constexpr。然而在why-isnt-a-for-loop-a-compile-time-expression这个问题中,作者给出了他的代码如下:

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

它不能被 GCC5.1 编译,但是,在用没有指定模板参数的东西替换 std::get&lt;i&gt;(t); 之后,他的代码确实有效。 在阅读了这个问题下的答案后,我发现他们的主要观点是constexpr for 给编译器带来了麻烦,因此在运行时使用了 for-loop。 所以这让我很困惑,一方面for-loop是在运行时,另一方面是循环在constexpr函数中,所以必须在编译时计算,所以似乎有矛盾。我只是想知道我在哪里犯了错误。

【问题讨论】:

标签: c++ c++11 c++14 constexpr


【解决方案1】:

您的函数需要在运行时和编译时都有效。因此,您的 Variant i 可以被视为运行时变量。如果函数在编译时执行,则变量i 是编译器运行时的一部分。所以 constexpr 函数中的 int 与非 constexpr 函数中的 int 遵循相同的规则。

您可以做的是创建自己的 constexpr for 循环:

template<typename F, std::size_t... S>
constexpr void static_for(F&& function, std::index_sequence<S...>) {
    int unpack[] = {0,
        void(function(std::integral_constant<std::size_t, S>{})), 0)...
    };

    (void) unpack;
}

template<std::size_t iterations, typename F>
constexpr void static_for(F&& function) {
    static_for(std::forward<F>(function), std::make_index_sequence<iterations>());
}

然后,您可以像这样使用static_for:

static_for<std::tuple_size<T>::value>([&](auto index) {
    std::get<index>(t);
});

请注意,在 C++17 之前,lambda 函数不能在 constexpr 函数中使用,因此您可以使用自己的仿函数:

template<typename T>
struct Iteration {
    T& tuple;

    constexpr Iteration(T& t) : tuple{t} {}

    template<typename I>
    constexpr void operator() (I index) const {
        std::get<index>(tuple);
    }
};

现在你可以像这样使用static_for:

static_for<std::tuple_size<T>::value>(Iteration{t});

【讨论】:

  • 为什么这么复杂?这还不够吗:template&lt;typename F&gt; constexpr void static_for_lt(F &amp;&amp; function, size_t from, size_t to) { if (from &lt; to) { static_for_lt(std::forward&lt;F&gt;(function), from + 1, to); } }?
  • @Andry 我很难理解你的代码 sn-p。首先,您永远不要致电function。此外,您的代码不会使from 或to 可用作function 主体的编译时常量。
  • 无法编辑我的评论。我的意思是 if 中的调用。为什么这不能解决问题?
  • 我为 constexpr 函数中不允许的声明找到了更好的解决方法:template &lt;typename T&gt; void static_consume(std::initializer_list&lt;T&gt;) {} template&lt;typename F, std::size_t... S&gt; constexpr void static_for(F &amp;&amp; function, std::index_sequence&lt;S...&gt;) { return static_consume({ (function(std::integral_constant&lt;std::size_t, S&gt;{}), 0)... }); }
  • @Andry 如果您愿意,您可以将此作为答案发布。我认为这样的代码在 C++11 中不值得做,因为缺少通用 lambda 和 C++14 允许在 constexpr 函数中声明。不过,它仍然可以帮助那些坚持使用 C++11 的人。
【解决方案2】:

这实际上与constexpr 函数是否需要能够在编译时可运行无关(tuple_size&lt;T&gt;::value 是一个常量表达式,无论T 是否来自constexpr反对与否)。

std::get&lt;&gt; 是一个函数模板,需要调用一个整型常量表达式。在这个循环中:

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

i 不是整数常量表达式。它甚至不是恒定的。 i 在整个循环中发生变化,并假定从 0 到 tuple_size&lt;T&gt;::value 的每个值。虽然看起来有点像,但这并不是调用具有不同 i 值的函数 - 这是每次调用不同的函数。目前该语言不支持这种迭代†,这与原始示例有很大不同,我们只是对整数求和。

也就是说,在一种情况下,我们循环遍历i 并调用f(i),而在另一种情况下,我们循环遍历i 并调用f&lt;i&gt;()。第二个比第一个有更多的先决条件。


†如果添加了此类语言支持,可能以constexpr for 语句的形式添加,那么该支持将独立于constexpr 函数。

【讨论】:

    【解决方案3】:

    Guillaume Racicot 已经提到了一些更好的方法,在Visual Studio 2015 Update 3 等编译器中,constexpr 支持和std::size 的支持有点未完成。

    #include <tuple>
    
    #include <stdio.h>
    #include <assert.h>
    
    // std::size is supported from C++17
    template <typename T, size_t N>
    constexpr size_t static_size(const T (&)[N]) noexcept
    {
        return N;
    }
    
    template <typename ...T>
    constexpr size_t static_size(const std::tuple<T...> &)
    {
        return std::tuple_size<std::tuple<T...> >::value;
    }
    
    template<typename Functor>
    void runtime_for_lt(Functor && function, size_t from, size_t to)
    {
        if (from < to) {
            function(from);
            runtime_for_lt(std::forward<Functor>(function), from + 1, to);
        }
    }
    
    template <template <typename T_> class Functor, typename T>
    void runtime_foreach(T & container)
    {
        runtime_for_lt(Functor<T>{ container }, 0, static_size(container));
    }
    
    template <typename Functor, typename T>
    void runtime_foreach(T & container, Functor && functor)
    {
        runtime_for_lt(functor, 0, static_size(container));
    }
    
    template <typename T>
    void static_consume(std::initializer_list<T>) {}
    
    template<typename Functor, std::size_t... S>
    constexpr void static_foreach_seq(Functor && function, std::index_sequence<S...>) {
        return static_consume({ (function(std::integral_constant<std::size_t, S>{}), 0)... });
    }
    
    template<std::size_t Size, typename Functor>
    constexpr void static_foreach(Functor && functor) {
        return static_foreach_seq(std::forward<Functor>(functor), std::make_index_sequence<Size>());
    }
    

    用法:

    using mytuple = std::tuple<char, int, long>;
    
    template <typename T>
    struct MyTupleIterator
    {
        T & ref;
    
        MyTupleIterator(T & r) : ref(r) {}
    
        void operator() (size_t index) const
        {
            // still have to do with switch
            assert(index < static_size(ref));
            size_t value;
            switch(index) {
                case 0: value = std::get<0>(ref); break;
                case 1: value = std::get<1>(ref); break;
                case 2: value = std::get<2>(ref); break;
            }
            printf("%u: %u\n", unsigned(index), unsigned(value));
        }
    };
    
    template <typename T>
    struct MyConstexprTupleIterator
    {
        T & ref;
    
        constexpr MyConstexprTupleIterator(T & r) : ref(r) {}
    
        constexpr void operator() (size_t index) const
        {
            // lambda workaround for:
            //  * msvc2015u3: `error C3250: 'value': declaration is not allowed in 'constexpr' function body`
            //  * gcc 5.x: `error: uninitialized variable ‘value’ in ‘constexpr’ function`
            [&]() {
              // still have to do with switch
              assert(index < static_size(ref));
              size_t value;
              switch(index) {
                  case 0: value = std::get<0>(ref); break;
                  case 1: value = std::get<1>(ref); break;
                  case 2: value = std::get<2>(ref); break;
              }
              printf("%u: %u\n", unsigned(index), unsigned(value));
            }();
        }
    };
    
    int main()
    {
        mytuple t = std::make_tuple(10, 20, 30);
        runtime_foreach<MyTupleIterator>(t);
    
        mytuple t2 = std::make_tuple(40, 50, 60);
        runtime_foreach(t2, [&](size_t index) {
            // still have to do with switch
            assert(index < static_size(t2));
            size_t value;
            switch(index) {
                case 0: value = std::get<0>(t2); break;
                case 1: value = std::get<1>(t2); break;
                case 2: value = std::get<2>(t2); break;
            }
            printf("%u: %u\n", unsigned(index), unsigned(value));
        });
    
        mytuple t3 = std::make_tuple(70, 80, 90);
        static_foreach<std::tuple_size<decltype(t3)>::value>(MyConstexprTupleIterator<mytuple>{t3});
    
        return 0;
    }
    

    输出:

    0: 10
    1: 20
    2: 30
    0: 40
    1: 50
    2: 60
    0: 70
    1: 80
    2: 90
    

    【讨论】:

      猜你喜欢
      • 2017-12-29
      • 2017-05-27
      • 1970-01-01
      • 1970-01-01
      • 2016-12-15
      • 2013-12-10
      • 1970-01-01
      • 1970-01-01
      • 2020-11-05
      相关资源
      最近更新 更多