c ++通用编译时for循环

c++ generic compile-time for loop

在某些情况下,可能 useful/necessary 在编译时有一个 for 循环 evaluated/unrolled。例如,要遍历 tuple 的元素,需要使用 std::get<I>,这取决于模板 int 参数 I,因此必须在编译时间。 使用编译递归可以解决特定问题,例如 , here, and, specifically for std::tuple here.

不过,我对如何实现 通用 编译时 for 循环很感兴趣。

下面的c++17代码实现了这个想法

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

template <int start, int end, template <int> class OperatorType, typename... Args>
void compile_time_for(Args... args)
{
  if constexpr (start < end)
         {
           OperatorType<start>()(std::forward<Args>(args)...);
           compile_time_for<start + 1, end, OperatorType>(std::forward<Args>(args)...);
         }    
}

template <int I>
struct print_tuple_i {
  template <typename... U>
  void operator()(const std::tuple<U...>& x) { std::cout << std::get<I>(x) << " "; }
};

int main()
{
  std::tuple<int, int, std::string> x{1, 2, "hello"};

  compile_time_for<0, 3, print_tuple_i>(x);

  return 0;
}

虽然代码有效,但如果能够简单地为例程 compile_time_for 提供模板函数,而不是在每次迭代时实例化模板 class 会更好。

但是,像下面这样的代码不会在 c++17

中编译
#include <utility>
#include <tuple>
#include <string>
#include <iostream>

template <int start, int end, template <int, typename...> class F, typename... Args>
void compile_time_for(F f, Args... args)
{
  if constexpr (start < end)
         {
           f<start>(std::forward<Args>(args)...);
           compile_time_for<start + 1, end>(f, std::forward<Args>(args)...);
         }    
}

template <int I, typename... U>
void myprint(const std::tuple<U...>& x) { std::cout << std::get<I>(x) << " "; }

int main()
{
  std::tuple<int, int, std::string> x{1, 2, "hello"};

  compile_time_for<0, 3>(myprint, x);

  return 0;
}

使用 gcc 7.3.0 和选项 std=c++17 第一个错误是

for2.cpp:7:25: error: ‘auto’ parameter not permitted in this context
 void compile_time_for(F f, Args... args)

问题是:

  1. 有没有办法编写 compile_time_for 以使其接受模板函数作为其第一个参数?
  2. 如果问题 1. 是肯定的,那么第一个工作代码是否有开销,因为例程在每次循环迭代时都会创建一个 OperatorType<start> 类型的对象?
  3. 是否计划在即将推出的 c++20 中引入类似编译时 for 循环的功能?

我将回答如何修复您上一个代码示例的问题。

编译不通过的原因在这里:

template <int start, int end, template <int, typename...> class F, typename... Args>
void compile_time_for(F f, Args... args)
                      /\

F 是一个模板,如果不替换模板参数,就不能拥有模板的对象 class。例如。您不能拥有 std::vector 类型的对象,但可以拥有 std::vector<int> 类型的对象。我建议你用模板 operator() 制作 F 仿函数:

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

template <int start, int end, typename F, typename... Args>
void compile_time_for(F f, Args... args)
{
  if constexpr (start < end)
         {
           f.template operator()<start>(std::forward<Args>(args)...);
           compile_time_for<start + 1, end>(f, std::forward<Args>(args)...);
         }    
}

struct myprint
{
    template <int I, typename... U>
    void operator()(const std::tuple<U...>& x) { std::cout << std::get<I>(x) << " "; }
};

int main()
{
  std::tuple<int, int, std::string> x{1, 2, "hello"};

  compile_time_for<0, 3>(myprint(), x);

  return 0;
}
  1. Is there a way to write compile_time_for such that it accepts a template function as its first argument?

简答:没有。

长答案:模板函数不是对象,是对象的集合,您可以将对象作为参数传递给函数,而不是对象的集合。

此类问题的通常解决方案是将模板函数包装在 class 中并传递 class 的对象(或简单的类型,如果函数包装为静态方法)。这正是您在工作代码中采用的解决方案。

  1. If question 1. is positive, is there an overhead in the first working code, due to the fact that the routine create an object of type OperatorType at every loop iteration?

问题 1 是否定的。

  1. Are there plans to introduce a feature like a compile-time for loop in the upcoming c++20?

我对 C++20 的了解不足以回答这个问题,但我想我没有传递一组函数。

无论如何,从 C++14 开始,您可以使用 std::make_index_sequence/std::index_sequence 进行某种编译时 for 循环。

例如,如果您接受在 myprint() 函数之外提取元组值,则可以将其包装在 lambda 中并编写如下内容(也使用 C++17 模板折叠;在 C+ 中+14有点复杂)

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

template <typename T>
void myprint (T const & t)
 { std::cout << t << " "; }

template <std::size_t start, std::size_t ... Is, typename F, typename ... Ts>
void ctf_helper (std::index_sequence<Is...>, F f, std::tuple<Ts...> const & t)
 { (f(std::get<start + Is>(t)), ...); }

template <std::size_t start, std::size_t end, typename F, typename ... Ts>
void compile_time_for (F f, std::tuple<Ts...> const & t)
 { ctf_helper<start>(std::make_index_sequence<end-start>{}, f, t); }

int main()
{
  std::tuple<int, int, std::string> x{1, 2, "hello"};

  compile_time_for<0, 3>([](auto const & v){ myprint(v); }, x);

  return 0;
}

如果你真的想在函数中提取元组元素(或多个元组元素),我能想到的最好的方法是将你的第一个例子转换如下

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

template <std::size_t start, template <std::size_t> class OT,
          std::size_t ... Is, typename... Args>
void ctf_helper (std::index_sequence<Is...> const &, Args && ... args)
 { (OT<start+Is>{}(std::forward<Args>(args)...), ...); }

template <std::size_t start, std::size_t end,
          template <std::size_t> class OT, typename... Args>
void compile_time_for (Args && ... args)
 { ctf_helper<start, OT>(std::make_index_sequence<end-start>{},
                         std::forward<Args>(args)...); }

template <std::size_t I>
struct print_tuple_i
 {
   template <typename ... U>
   void operator() (std::tuple<U...> const & x)
    { std::cout << std::get<I>(x) << " "; }
 };

int main()
{
  std::tuple<int, int, std::string> x{1, 2, "hello"};

  compile_time_for<0u, 3u, print_tuple_i>(x);

  return 0;
}

-- 编辑--

OP 询问

Is there some advantage of using index_sequence over my first code?

我不是专家,但这样可以避免递归。 编译器有递归限制,从模板的角度来看,这可能是严格的。这样你就可以避开它们。

Also, your code does not compile if you set the template parameters end > start. (One can imagine a situation where you want the compiler to determine if a loop is instantiated at all)

我想你的意思是如果 start > end.

我的代码无法编译

不好的部分是没有检查这个问题所以编译器在这种情况下也尝试编译我的代码;所以遇到

 std::make_index_sequence<end-start>{}

其中 end - start 是一个负数,但由需要无符号数的模板使用。所以 end - start 成为一个非常大的正数,这可能会导致问题。

你可以在 compile_time_for()

中强加一个 static_assert() 来避免这个问题
template <std::size_t start, std::size_t end,
          template <std::size_t> class OT, typename... Args>
void compile_time_for (Args && ... args)
 { 
   static_assert( end >= start, "start is bigger than end");

   ctf_helper<start, OT>(std::make_index_sequence<end-start>{},
                         std::forward<Args>(args)...);
 }

或者您可以使用 SFINAE 禁用该功能

template <std::size_t start, std::size_t end,
          template <std::size_t> class OT, typename... Args>
std::enable_if_t<(start <= end)> compile_time_for (Args && ... args)
 { ctf_helper<start, OT>(std::make_index_sequence<end-start>{},
                         std::forward<Args>(args)...); }

如果需要,您可以使用 SFINAE 添加重载 compile_time_for() 版本来管理 end < start 案例

template <std::size_t start, std::size_t end,
          template <std::size_t> class OT, typename ... Args>
std::enable_if_t<(start > end)> compile_time_for (Args && ...)
 { /* manage the end < start case in some way */ }