简体   繁体   English

c ++循环的通用编译时

[英]c++ generic compile-time for loop

In some contexts, it could be useful/necessary to have a for loop evaluated/unrolled at compile time. 在某些情况下,在编译时评估/展开for循环可能是有用/必要的。 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. 例如,要迭代tuple的元素,需要使用std::get<I> ,这取决于模板int参数I ,因此必须在编译时进行评估。 Using compile recursion one can solve a specific problem, as for instance discussed here , here , and, specifically for std::tuple here . 使用递归编写一个能解决一个具体问题,例如讨论在这里在这里 ,并专门针对std::tuple 在这里

I am interested, however, on how to implement a generic compile-time for loop. 我很感兴趣,但是,关于如何实现一个通用的编译时for循环。

The following c++17 code implements this idea 以下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;
}

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. 虽然代码有效,但是能够简单地向例程compile_time_for提供模板函数,而不是在每次迭代时实例化的模板类会更好。

A code like the following, however, does not compile in c++17 但是,如下所示的代码不能在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;
}

With gcc 7.3.0 and option std=c++17 the first error is 使用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)

The questions are: 问题是:

  1. Is there a way to write compile_time_for such that it accepts a template function as its first argument? 有没有办法编写compile_time_for ,以便它接受模板函数作为它的第一个参数?
  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? 如果问题1是肯定的,那么第一个工作代码中是否存在开销,因为例程在每次循环迭代时都会创建一个类型为OperatorType<start>的对象?
  3. Are there plans to introduce a feature like a compile-time for loop in the upcoming c++20 ? 是否有计划在即将推出的c++20引入类似编译时for循环的功能?
  1. Is there a way to write compile_time_for such that it accepts a template function as its first argument? 有没有办法编写compile_time_for,以便它接受模板函数作为它的第一个参数?

Short answer: no. 简答:不。

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. 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是肯定的,那么第一个工作代码中是否存在开销,因为例程在每次循环迭代时都会创建一个类型为OperatorType的对象?

Question 1 is negative. 问题1是否定的。

  1. Are there plans to introduce a feature like a compile-time for loop in the upcoming c++20? 是否有计划在即将推出的c ++ 20中引入类似编译时for循环的功能?

I don't know C++20 enough to respond this question but I suppose not passing a set of function. 我不知道C ++ 20是否足以回答这个问题,但我想不会传递一组函数。

Anyway, you can do a sort of compile-time for loop using std::make_index_sequence / std::index_sequence starting from C++14. 无论如何,你可以使用从C ++ 14开始的std::make_index_sequence / std::index_sequence进行一种编译时循环。

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) 例如,如果您接受在myprint()函数之外提取touple值,则可以将其包装在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;
}

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

-- EDIT -- - 编辑 -

The OP asks OP问道

Is there some advantage of using index_sequence over my first code? 使用index_sequence比我的第一个代码有一些优势吗?

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. 这样你就可以避免它们。

Also, your code does not compile if you set the template parameters end > start . 此外,如果设置模板参数end > start ,则代码不会编译。 (One can imagine a situation where you want the compiler to determine if a loop is instantiated at all) (可以想象一种情况,您希望编译器确定是否实例化了一个循环)

I suppose you mean that my code does not compile if start > end . 我想你的意思是我的代码在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>{}

where end - start is a negative number but used by a template that expect an unsigned number. end - start是一个负数,但是由期望无符号数的模板使用。 So end - start become a very great positive number and this can cause problems. 所以end - start成为一个非常好的正数,这可能会导致问题。

You can avoid this problem imposing a static_assert() inside compile_time_for() 你可以避免在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)...);
 }

Or maybe you can use SFINAE to disable the function 或者也许你可以使用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)...); }

If you want, using SFINAE you can add an overloaded compile_time_for() version to manage the end < start case 如果需要,可以使用SFINAE添加重载的compile_time_for()版本来管理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 */ }

I'll answer on the question how to fix your last code sample. 我将回答有关如何修复上一个代码示例的问题。

The reason why it doesn't compile is here: 它不编译的原因在于:

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

F is a template, you can't have an object of a template class without template parameters being substituted. F是一个模板,如果没有替换模板参数,则不能拥有模板类的对象。 Eg you can't have on object of std::vector type, but can have object of std::vector<int> . 例如,你不能拥有std::vector类型的对象,但可以拥有std::vector<int> I suggest you to make F functor with a template 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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM