【问题标题】:For loop over template arguments/typesFor循环模板参数/类型
【发布时间】:2014-06-03 12:51:04
【问题描述】:

我想为几个可能的类的几种组合编写基准代码。如果我自己编写每个组合,它就会变成无法维护的混乱。因此,我正在寻找一种通过模板自动组合每种类型的方法,类似于以下伪代码:

for (typename HashFuction : Sha256, Sha512, Sa512_256, Sha3_256, Sha3_512) {
   for (typename KeyingWrapper : TwoPassKeyedHash, OnePassKeyedHash, PlainHash) {
      for (typename InstantiatedGetLeaf: GetLeaf<8>, GetLeaf<1024>) {
         for (typename algorithm : algA, algB, algC) {
            runAndTime<HashFunction,KeyingWrapper,
                       InstantiatedGetLeaf,algorithm>(someArgs);
         }
       }
    }
 }

Sha256,... ,TwoPassKeyedHash,... 是类型。

我正在寻找的代码应该在功能上等同于以下内容:

runAndTime<Sha256,TwoPassKeyedHash,GetLeaf<8>,algA>(someArgs);
runAndTime<Sha256,TwoPassKeyedHash,GetLeaf<8>,algB>(someArgs);
runAndTime<Sha256,TwoPassKeyedHash,GetLeaf<8>,algC>(someArgs);

runAndTime<Sha256,TwoPassKeyedHash,GetLeaf<1024>,algA>(someArgs);
runAndTime<Sha256,TwoPassKeyedHash,GetLeaf<1024>,algB>(someArgs);
runAndTime<Sha256,TwoPassKeyedHash,GetLeaf<1024>,algC>(someArgs);

runAndTime<Sha256,OnePassKeyedHash,GetLeaf<8>,algA>(someArgs);
runAndTime<Sha256,OnePassKeyedHash,GetLeaf<8>,algB>(someArgs);
runAndTime<Sha256,OnePassKeyedHash,GetLeaf<8>,algC>(someArgs);

// And 99 further lines…

在 Peregring-lk 的帮助下,我已经走到了这么远

#include <iostream>

template<typename Aux_type>
void test_helper()
{}

template<typename Aux_type, typename Head, typename... Tail>
void test_helper() {
   std::cout << Head::i;
   test_helper<Aux_type, Tail...>();
}

template<typename... Args>
void test()
{
    test_helper<void, Args...>();
}

struct A{
   static const int i=1;
};

struct B{
   static const int i=2;
};

int main() {
   test<A, B>();
   return 0;
}

但我还没有看到如何迭代该递归以获得嵌套循环。任何帮助将不胜感激。

(编辑:代码重组并包含 Peregring-lk 的答案。)

【问题讨论】:

标签: c++ templates metaprogramming


【解决方案1】:

有时了解您的目标会有所帮助:

  • 您需要几种参数类型
  • 对于每种参数类型,有几个可能的“值”

并且想要对每一个值组合应用一些东西(一次每个参数类型一个)。

这看起来可以表达:

combine<
    Set<Sha256, Sha512, Sa512_256, Sha3_256, Sha3_512>,
    Set<TwoPassKeyedHash, OnePassKeyedHash, PlainHash>,
    Set<GetLeaf<8>, GetLeaf<1024>>,
    Set<algA, algB, algC>
>(runAndTime);

如果runAndTime 是以下实例:

struct SomeFunctor {
   template <typename H, typename W, typename L, typename A>
   void operator()(cons<H>{}, cons<W>{}, cons<L>{}, cons<A>{});
};

cons 只是将类型作为常规参数传递的一种方式(容易得多)。

我们走吧?


首先,一些传递类型的方法(便宜):

template <typename T>
struct cons { using type = T; };

template <typename... T>
struct Set {};

一个明确的bind(里面没有魔法):

template <typename F, typename E>
struct Forwarder {
    Forwarder(F f): inner(f) {}

    template <typename... Args>
    void operator()(Args... args) { inner(cons<E>{}, args...); }

    F inner;
}; // struct Forwarder

现在我们深入研究手头的真正任务:

  • 我们需要迭代类型集
  • 在一个集合中,我们需要迭代它的元素(类型也是)

这需要两个级别的调度:

template <typename FirstSet, typename... Sets, typename F>
void combine(F func);

template <typename Head, typename... Tail, typename... Sets, typename F>
void apply_set(F func, Set<Head, Tail...>, Sets... others);

template <typename... Sets, typename F>
void apply_set(F func, Set<>, Sets... others);

template <typename E, typename NextSet, typename... Sets, typename F>
void apply_item(F func, cons<E>, NextSet, Sets...);

template <typename E, typename F>
void apply_item(F func, cons<E> e);

其中combine 是外部(公开)函数,apply_set 用于迭代集合,apply_item 用于迭代集合内的类型。

实现很简单:

template <typename Head, typename... Tail, typename... Sets, typename F>
void apply_set(F func, Set<Head, Tail...>, Sets... others) {
    apply_item(func, cons<Head>{}, others...);

    apply_set(func, Set<Tail...>{}, others...);
} // apply_set

template <typename... Sets, typename F>
void apply_set(F, Set<>, Sets...) {}

template <typename E, typename NextSet, typename... Sets, typename F>
void apply_item(F func, cons<E>, NextSet ns, Sets... tail) {
    Forwarder<F, E> forwarder(func);

    apply_set(forwarder, ns, tail...);
}

template <typename E, typename F>
void apply_item(F func, cons<E> e) {
    func(e);
} // apply_item


template <typename FirstSet, typename... Sets, typename F>
void combine(F func) {
    apply_set(func, FirstSet{}, Sets{}...);
} // combine

对于apply_setapply_item 中的每一个,我们都有一个递归案例和一个基本案例,尽管这里是某种共同递归,因为apply_item 回调apply_set

还有一个简单的例子:

struct Dummy0 {}; struct Dummy1 {}; struct Dummy2 {};
struct Hello0 {}; struct Hello1 {};

struct Tested {
    Tested(int i): value(i) {}

    void operator()(cons<Dummy0>, cons<Hello0>) { std::cout << "Hello0 Dummy0!\n"; }
    void operator()(cons<Dummy0>, cons<Hello1>) { std::cout << "Hello1 Dummy0!\n"; }
    void operator()(cons<Dummy1>, cons<Hello0>) { std::cout << "Hello0 Dummy1!\n"; }
    void operator()(cons<Dummy1>, cons<Hello1>) { std::cout << "Hello1 Dummy1!\n"; }
    void operator()(cons<Dummy2>, cons<Hello0>) { std::cout << "Hello0 Dummy2!\n"; }
    void operator()(cons<Dummy2>, cons<Hello1>) { std::cout << "Hello1 Dummy2!\n"; }

    int value;
};

int main() {
    Tested tested(42);
    combine<Set<Dummy0, Dummy1, Dummy2>, Set<Hello0, Hello1>>(tested);
}

you can witness live on Coliru 打印:

Hello0 Dummy0!
Hello1 Dummy0!
Hello0 Dummy1!
Hello1 Dummy1!
Hello0 Dummy2!
Hello1 Dummy2!

享受:)

注意:假设仿函数复制起来很便宜,否则在传递和将其存储在Forwarder 时都可以使用引用。

编辑:删除了 Set 周围的 cons(它出现的所有地方),这是不必要的。

【讨论】:

  • 很好的答案,如果可以的话,我会投票两次:D。为了完整起见,来自 Coliru 的编译器选项:-std=c++11 -O2 -Wall -pedantic,使用 clang++ 3.4 和 g++ 4.8.2 测试
  • 还有一个问题。您的cons 课程的目的是什么?例如,没有默认构造函数的类型传输?还是有其他黑暗目的?
  • 啊好吧,避免繁重的副本(例如)。这是有道理的。
  • @Peregring-lk:是的,cons 只是一种传递类型而不实际构建对象的方法;既是因为所述对象可能没有默认构造函数,也是因为不清楚是否可以传递引用(或者基准是否每次都要求创建一个新的)。顺便说一句,我对解决方案进行了一些手术:我将所有cons&lt;Set&lt;...&gt;&gt; 转换为Set&lt;...&gt;,因为cons 在那里是多余的。我也更新了 Coliru 上的实时示例,它产生了相同的结果。
【解决方案2】:

函数不允许部分特化,除非特化是完整的。每个新的不同函数签名都声明一个新的重载,除非它们的签名完全相同。

试试下面的代码:

#include <iostream>

template<typename Aux_type>
void test_helper()
{}

template<typename Aux_type, typename Head, typename... Tail>
void test_helper() {
   std::cout << Head::i;
   test_helper<Aux_type, Tail...>();
}

template<typename... Args>
void test()
{
    test_helper<void, Args...>();
}

struct A{
   static const int i=1;
};

struct B{
   static const int i=2;
};

int main() {
   test<A, B>();
   return 0;
}

it does compile(并打印12)。

无论如何,我还没有理解你的伪代码示例。

【讨论】:

  • 谢谢,这很有启发性。我已经编辑了我的问题,以使核心问题更加清晰。
【解决方案3】:

我认为 C++ 在方便灵活的代码生成方面不是合适的工具......只需使用您选择的脚本语言编写一个简单的实用程序,例如在 python 中:

generate_test_code.py:

#!/usr/bin/python
for HashFuction in {"Sha256", "Sha512", "Sa512_256", "Sha3_256", "Sha3_512"}:
    for KeyingWrapper in {"TwoPassKeyedHash", "OnePassKeyedHash", "PlainHash"}:
        for InstantiatedGetLeaf in {"GetLeaf<8>", "GetLeaf<1024>"}:
            for Algorithm in {"algA", "algB", "algC"}:
                print("runAndTime<{},{},{},{}>(someArgs);".format(HashFuction,KeyingWrapper,InstantiatedGetLeaf,Algorithm))

...然后在您的 Makefile 中:

generated_test_code.cpp: generate_test_code.py
    python generate_test_code.py > generated_test_code.cpp

...在你的 c++ 代码中,只需在你想要的地方 #include "generated_test_code.cpp"。

【讨论】:

  • 当您使用 python3 时,我建议#!/usr/bin/python3 以获得更好的兼容性。感谢您提供更实用的额外答案。
  • 我刚刚尝试在python2.7下运行它,似乎运行良好。
  • 哦,对了,python3 print() 函数只有一个参数,python2 print 命令后面的括号不会被解释为元组,而是多余的括号。在批评之前我应该​​用python2实际测试过它,对不起。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-10-23
  • 1970-01-01
  • 2012-07-25
  • 2012-01-07
  • 1970-01-01
  • 2021-02-10
  • 2011-10-08
相关资源
最近更新 更多