C ++通用编译时循环 [英] c++ generic compile-time for loop

查看:71
本文介绍了C ++通用编译时循环的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在某些情况下,在编译时评估/展开 for 循环可能是有用/必要的。例如,要遍历元组的元素,需要使用 std :: get< I> ,这取决于模板 int I I ,因此必须在编译时进行评估。
使用编译递归可以解决一个特定的问题,例如在此处此处,并且专门针对 std :: tuple 在此处

In some contexts, it could be useful/necessary to have a for loop evaluated/unrolled at compile time. For example, to iterate over the elements of a tuple, one needs to use std::get<I>, which depends on a template int parameter I, hence it has to be evaluated at compile time. Using compile recursion one can solve a specific problem, as for instance discussed here, here, and, specifically for std::tuple here.

但是,我对如何实现泛型编译时用于循环。

I am interested, however, on how to implement a generic compile-time for loop.

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

The following c++17 code implements this idea

#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 的模板函数,而不是在每次迭代时实例化的模板类。

While the code works, it would be nicer to be able to simply provide a template function to the routine compile_time_for, rather than a template class to be instantiated at each iteration.

类似于但是,以下内容无法在 c ++ 17

A code like the following, however, does not compile in 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 第一个错误是

With gcc 7.3.0 and option std=c++17 the first error is

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. 是否计划在即将推出的循环中引入诸如编译时for循环之类的功能 c ++ 20

  1. Is there a way to write compile_time_for such that it accepts a template function as its first argument?
  2. 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<start> at every loop iteration?
  3. Are there plans to introduce a feature like a compile-time for loop in the upcoming c++20?


推荐答案



  1. 是否可以编写compile_time_for使其接受模板函数作为第一个参数?


简短答案:否。

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

Long answer: a template function isn't an object, is a collection of objects and you can pass to a function, as an argument, an object, non a collection of objects.

解决此类问题的常用方法是包装模板函数在类内传递该类的对象(如果函数包装为静态方法,则传递类型)。正是您在工作代码中采用的解决方案。

The usual solution to this type of problem is wrap the template function inside a class and pass an object of the class (or simply the type, if the function is wrapped as a static method). That is exactly the solution you have adopted in your working code.



  1. 如果问题1是肯定的,由于例程在每次循环迭代时都会创建类型为OperatorType的对象,因此在第一个工作代码中会产生开销吗?


问题1为否。



  1. 是否计划在即将到来的c ++ 20中引入诸如编译时for循环之类的功能?


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

I don't know C++20 enough to respond this question but I suppose not passing a set of function.

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

Anyway, you can do a sort of compile-time for loop using std::make_index_sequence/std::index_sequence starting from C++14.

例如,如果您接受将值提取到 myprint()函数之外,则可以对其进行包装在lambda内并写如下所示(也使用C ++ 17模板折叠;在C ++ 14中会更复杂)

By example, if you accept to extract the touple value outside your myprint() function, you can wrap it inside a lambda and write something as follows (using also C++17 template folding; in C++14 is a little more complicated)

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

如果您确实要提取函数内部的元组元素(或元组元素) ,我能想到的最好的办法就是按如下所示变换您的第一个示例

If you really want extract the tuple element (or tuples elements) inside the function, the best I can imagine is transform your first example as follows

#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询问


相对于我的第一个代码,使用index_sequence是否有优势?

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

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

I'm not an expert but this way you avoid recursion. Compilers have recursion limits, from the template point of view, that can be strict. This way you avoid they.


此外,如果您设置模板参数 end> ;,则代码不会编译。开始。 (可以想象一种情况,您希望编译器确定循环是否完全实例化)

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>代码不会编译结束

I suppose you mean that my code does not compile if start > end.

不好的地方是,没有检查此问题,因此在这种情况下,编译器也会尝试编译我的代码;因此遇到

The bad part is that there aren't check about this problem so the compiler try to compile my code also in this case; so encounter

 std::make_index_sequence<end-start>{}

其中 end-start 是一个负数,但由期望使用无符号数的模板使用。因此 end-start 变成一个非常大的正数,这可能会导致问题。

where end - start is a negative number but used by a template that expect an unsigned number. So end - start become a very great positive number and this can cause problems.

您可以避免出现此问题在 compile_time_for()

You can avoid this problem imposing a static_assert() inside compile_time_for()

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禁用该功能

Or maybe you can use SFINAE to disable the function

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<开始 case

If you want, using SFINAE you can add an overloaded compile_time_for() version to manage the end < start case

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 */ }

这篇关于C ++通用编译时循环的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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