【问题标题】:Iterate with constexpr用 constexpr 迭代
【发布时间】:2017-08-29 09:24:02
【问题描述】:

我想写这样的东西:

template<int i> void f() {}

for (constexpr int i : {1,2,3})
{
    f<i>();
}

是否可以在 constexpr 上进行迭代?

谢谢

【问题讨论】:

    标签: c++ c++11


    【解决方案1】:

    正如您可能理解的那样,您不能这样做:

    for (constexpr int i : {1,2,3})
    {
        f<i>();
    }
    

    因为,如果i 在一个循环中从 1 变为 3,那么它就是一个 变量 并且 不是编译时常量。并且变量不能是模板参数, 如f&lt;i&gt;:只有编译时常量可以是模板参数。

    在 C++11 及更高版本中,感谢variadic templates, 您可以有效地迭代编译时常量的任意序列 通过使用接受合适的任意序列的模板函数的编译时递归 模板参数。

    如果您还不知道该怎么做,那对您来说几乎没有任何意义。 这是一个 C++11 示例,可以满足您的要求:

    #include <type_traits>
    #include <iostream>
    
    template<int i> void f()
    {
        std::cout << i << '\n';
    }
    
    // This overload is chosen when there is only 1 template argument.
    template<int First, int ...Rest>
    typename std::enable_if<sizeof...(Rest) == 0>::type
    for_each_f()
    {
        f<First>();
    }
    
    // This overload is chosen when there is > 1 template argument.
    template<int First, int ...Rest>
    typename std::enable_if<sizeof...(Rest) != 0>::type
    for_each_f()
    {
        f<First>();
        for_each_f<Rest...>();
    }
    
    int main()
    {
        for_each_f<2,3,5,7,11>();
        return 0;
    }
    

    See it live

    除了可变参数模板,这种技术依赖于非常重要的 C++ 元编程 SFINAE 的原理和std::enable_if 的原理, 这是标准 C++ 库提供的用于利用 SFINAE 的工具。

    101010 的回答展示了一种更复杂、更强大的风格 在 C++14 中可用的解决方案(并且很容易在 C++11 中实现) 如果你写一些支持样板文件)。

    【讨论】:

    • TBH 我更喜欢这种方法。在 int t[] 的初始化(C++ 的错,不是 101010 的错)周围有如此很多可怕的骇客,在我选择走这条路之前,我必须得到很多钱!
    • 如果将模板签名更改为template&lt;int First&gt; for_each_f();template&lt;int First, int Second, int ...Rest&gt; for_each_f();,就可以去掉enable_ifs
    【解决方案2】:

    不,您不能在编译时使用 for 循环进行迭代。 C++中的for控制结构用于运行时控制流。

    但是,您可以使用其他编译时工具。例如,在 C++ 14 中,您可以通过以下方式实现您想要的:

    1. 定义一个模板包装类来调用你的函数。

      template<int i>
      struct wrapper {
        void operator()() const { f<i>(); }
      };
      
    2. 使用std::index_sequence 生成编译时索引。

      template<template<int> class W, std::size_t... I>
      void caller_impl(std::index_sequence<I...>) {
        int t[] = { 0, ((void)W<I>()(), 1)... };
        (void) t;
      }  
      
      template<template<int> class W, std::size_t N, typename Indices = std::make_index_sequence<N>>
      void call_times() {
        caller_impl<W>(Indices());
      }
      
    3. 然后调用为

      int main() {
        call_times<wrapper, 42>();
      }
      

      Live Demo

    如果 C++14 不是一个选项,您可以查看 here 了解如何实现 std::index_sequence 自己。

    【讨论】:

    • (void) t 是干什么用的?
    • @DeanSeo 禁止未使用变量的编译器警告。
    • 如果你有c++17,那么caller_impl函数体可以是(W&lt;I&gt;()(), ... );更容易理解。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-24
    • 2017-09-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多