我有一个类,取决于整数模板参数。在我的程序中的某一点,我想使用此模板的一个实例,具体取决于在运行时确定的此参数的值。这是一个简单的例子,演示了如何使用一个大的switch语句来解决这个问题:
#include <string>
#include <iostream>
#include <type_traits>
template<unsigned A>
struct Wrapper {
typedef typename std::conditional<A==1, int, float>::type DataType;
DataType content[A];
void foo() {
std::cout << A << std::endl;
};
};
int main(int argc, char *argv[])
{
std::string arg = argv[1];
int arg_int = std::stoi(arg);
switch (arg_int) {
case 1: {
Wrapper<1> w;
w.foo();
break;
}
case 2: {
Wrapper<2> w;
w.foo();
break;
}
case 3: {
Wrapper<3> w;
w.foo();
break;
}
default:
return 1;
};
return 0;
}
一旦我不仅有一个参数A
,而且各种组合中有多个模板参数,这将很快变得难以处理。我们还假设实际上有一个很好的理由将A作为模板参数实现。
有没有办法用几乎相同的case语句替换巨大的switch语句,例如使用Boost的一些元编程魔法或预处理器黑客?
理想情况下,我希望能够编写如下内容:
INSTANTIATE_DEPENDING(i, {1, 2, 3},
{
Wrapper<i> w;
w.foo();
}
);
答案 0 :(得分:16)
您可以使用可变参数模板,可能是这样的:
#include <cstdlib>
#include <string>
int main(int argc, char * argv[])
{
if (argc != 2) { return EXIT_FAILURE; }
handle_cases<1, 3, 4, 9, 11>(std::stoi(argv[1]));
}
实现:
template <int ...> struct IntList {};
void handle_cases(int, IntList<>) { /* "default case" */ }
template <int I, int ...N> void handle_cases(int i, IntList<I, N...>)
{
if (I != i) { return handle_cases(i, IntList<N...>()); }
Wrapper<I> w;
w.foo();
}
template <int ...N> void handle_cases(int i)
{
handle_cases(i, IntList<N...>());
}
答案 1 :(得分:6)
arg_int是一个运行时参数,因此无法将其直接附加到模板参数。你可以使用某种处理程序表来删除这里的switch语句。
你会使用像lookup_handler( int N )
这样的东西返回一个类型handler
,这可能是一个lambda调用其中一个模板函数。
在桌面上注册所有lambda可以从你允许的最高编号开始递归完成。
template< unsigned N > register_lambda()
{
table.add( Wrapper<N>() );
register_lambda< N-1 >;
}
并专注于register_lambda<0>
然后你拨打register_lambda<32>
的地方说你已经注册了从0到32的所有数字。
实现这样一个表的一种方法是:
class lambda_table
{
typedef std::function<void()> lambda_type;
public:
void add( lambda_type );
bool lookup( size_t key, lambda_type & lambda ) const;
};
从main()或想要调用它的任何地方,你都有对这个表的引用(称之为表),然后调用
lambda_type lambda;
if( table.find( arg_int, lambda ) )
lanbda();
else
default_handler();
您可以更改此选项以使表本身成为默认处理程序,其中没有为此数字提供任何处理程序。
尽管lambdas可以包装所有类型的数据成员,但实际上您可能希望模板是层次结构中的类而不是lambdas,因为它们中包含数据存储。
答案 2 :(得分:3)
只使用宏!
template<unsigned A>
struct Wrapper {
int content[A];
void foo() { };
};
#define WRAPPER_SWITCH_CASE(i) case i: Wrapper<i>().foo(); break;
int main(int argc, char *argv[])
{
std::string arg = argv[1];
int arg_int = std::stoi(arg);
switch (arg_int) {
WRAPPER_SWITCH_CASE(1)
WRAPPER_SWITCH_CASE(2)
WRAPPER_SWITCH_CASE(3)
default: return 1;
};
return 0;
}
但是如你所知,宏是有害的;我认为Wrapper
应该在运行时分配content
,而不是模板。
答案 3 :(得分:2)
使用Wrappers的递归生成器的简短概念应用程序:
#include <iostream>
#include <vector>
struct FooProvider
{
virtual void foo() = 0;
};
template<unsigned A>
struct Wrapper : public FooProvider {
Wrapper() {std::cout << A << std::endl;}
int content[A];
virtual void foo() { std::cout << "call:" << A << std::endl;};
};
static std::vector<FooProvider*> providers;
template <unsigned CTR>
struct Instantiator
{
Instantiator()
{
providers.insert(providers.begin(), new Wrapper<CTR>);
Instantiator<CTR - 1>();
}
};
template <>
struct Instantiator<0>
{
Instantiator() {}
};
int main()
{
Instantiator<100>();
providers[4]->foo();
// do not forget to delete the providers
}
答案 4 :(得分:2)
您可以使用更高阶的循环宏将块实现传递给通用循环扩展器:
#define M_NARGS(...) M_NARGS_(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define M_NARGS_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N
#define M_CONC(A, B) M_CONC_(A, B)
#define M_CONC_(A, B) A##B
#define M_ID(...) __VA_ARGS__
#define M_FOR_EACH(ACTN, ...) M_CONC(M_FOR_EACH_, M_NARGS(__VA_ARGS__)) (ACTN, __VA_ARGS__)
#define M_FOR_EACH_0(ACTN, E) E
#define M_FOR_EACH_1(ACTN, E) ACTN(E)
#define M_FOR_EACH_2(ACTN, E, ...) ACTN(E) M_FOR_EACH_1(ACTN, __VA_ARGS__)
#define M_FOR_EACH_3(ACTN, E, ...) ACTN(E) M_FOR_EACH_2(ACTN, __VA_ARGS__)
#define M_FOR_EACH_4(ACTN, E, ...) ACTN(E) M_FOR_EACH_3(ACTN, __VA_ARGS__)
#define M_FOR_EACH_5(ACTN, E, ...) ACTN(E) M_FOR_EACH_4(ACTN, __VA_ARGS__)
//...etc
#define INSTANTIATE_DEPENDING(L, C) M_FOR_EACH(C, M_ID L)
//...
#define CASE_BLOCK(n) case n: { Wrapper<n> w; w.foo(); break; }
INSTANTIATE_DEPENDING((1, 2, 3), CASE_BLOCK)
#undef CASE_BLOCK //if you like, not essential to the concept
关于这一点的说法不多:循环重复了传递列表长度的块,将列表中的项目传递给要扩展的宏。因此,您将实现放在该宏中(如果您希望它是本地的话,请#undef
。)
更优雅(让您嵌套参数化代码以扩展其所属的表达式,而不是第二个定义),您可以使用相当高端的Order metaprogramming library:
#include <order/interpreter.h>
ORDER_PP( // runs Order code
8for_each_in_range(8fn(8I,
8print( (case) 8I (: { )
(Wrapper<) 8I (> w; w.foo(); break; }) )),
1, 4)
)
(对于非连续列表使用8for-each
而不是8for_each_in_range
。订单具有完整的函数式编程语义,所以这些都是小问题。)
答案 5 :(得分:1)
作为开关的一般替代方法,您可以使用向量或函数指针映射来移除开关:
template <int i>
int foo()
{
Wrapper<i> w;
w.foo();
return i;
}
static std::vector<int(*)()> m;
void init()
{
m.push_back(&foo<0>);
m.push_back(&foo<1>);
}
void bar(int i)
{
m[i]();
}
在C ++ 11中,您可以使用初始化列表初始化矢量或地图。
答案 6 :(得分:1)
这是另一种方法:
template<int N>
void do_foo()
{
Wrapper<N> w;
w.foo();
}
template<int N, int... Ns>
struct fn_table : fn_table<N - 1, N - 1, Ns...>
{
};
template<int... Ns>
struct fn_table<0, Ns...>
{
static constexpr void (*fns[])() = {do_foo<Ns>...};
};
template<int... Ns>
constexpr void (*fn_table<0, Ns...>::fns[sizeof...(Ns)])();
int main(int argc, char *argv[])
{
std::string arg = argv[1];
int arg_int = std::stoi(arg);
// 4 if you have Wrapper<0> to Wrapper<3>.
fn_table<4>::fns[arg_int]();
}
答案 7 :(得分:1)
受Kerrek SB's answer的可变参数模板的启发,这是一个可以轻松扩展到任何类型的多个参数的解决方案:
template <int param1_>
struct Params
{
const static int kParam1 = param1_;
// Add other parameters here if needed
};
// Default case: list is empty
template <typename T>
void handle_cases(const T param1) { }
// Regular case: list is not-empty
template <typename T, typename head, typename ...tail>
void handle_cases(const T param1)
{
if (head::kParam1 == param1)
{
Wrapper<head::kParam1> w;
w.foo();
}
else
{
handle_cases<T, tail...>(param1);
}
}
请注意,typename T
只是附加模板参数的一个示例,它不是头/尾列表的一部分。
以下是如何使用它:
int main(int argc, char * argv[])
{
if (argc != 2) { return EXIT_FAILURE; }
handle_cases<int, Params<1>, Params<3>, Params<4>, Params<9>, Params<11>>(std::stoi(argv[1]));
}
答案 8 :(得分:0)
要解释基于静态函数表的@Simple解决方案:
#include <iostream>
#include <vector>
using namespace std;
template<int N>
void do_foo()
{
cout << N << endl;
}
template<int N, int... Ns>
struct fn_table : fn_table<N - 1, N - 1, Ns...> {
};
template<int... Ns>
void p()
{
int a[] = {Ns...};
for (int i = 0; i < sizeof(a)/sizeof(int); ++i)
cout << a[i] << endl;
}
// Recursion-base instantiation with leading 0 parameter.
template<int... Ns>
struct fn_table<0, Ns...> {
// calling fn_table<4> would call recursively with template parameters: <4>, <3, 3>, <2, 2, 3>, <1, 1, 2, 3>, <0, 0, 1, 2, 3>. The last call would create 4 (we expand Ns without the first 0) do_foo functions using a variadic parameter pack "...".
static constexpr void (*fns[])() = {
p<Ns...> // call a function that prints Ns... for illustration, expanding the parameters inside p instead of duplicating it.
//do_foo<Ns>...
};
};
template<int... Ns>
constexpr void (*fn_table<0, Ns...>::fns[sizeof...(Ns)])();
int main(int argc, char *argv[])
{
int arg_int = 0;
// 4 if you have Wrapper<0> to Wrapper<3>.
fn_table<4>::fns[arg_int]();
}
答案 9 :(得分:0)
使用integer_sequence构建表。我还添加了:i)序列开始,ii)函数类型的参数,例如接收和返回值。
#include <iostream>
#include <vector>
using namespace std;
struct Foo {
template<int N>
static void foo(int &a) {
cout << N << endl;
a = N + 1;
}
};
template<int start, typename F, typename R, typename T, T... ints>
auto fn_table_( integer_sequence<T, ints...> int_seq )
{
vector<R> expand = { F::foo<ints+start>... };
vector<R> dummy( start );
expand.insert( expand.begin(), dummy.begin(), dummy.end() );
return expand;
}
template<int start, typename F, typename R, int N>
auto fn_table()
{
return fn_table_<start, F, R>( make_integer_sequence<int, N-start>{} );
}
void main()
{
int arg_int = 5;
typedef void (*fun_type)( int & );
auto fns = fn_table<4, Foo, fun_type, 7>();
int a;
fns[arg_int]( a );
cout << a << endl;
cout << "all:\n";
for (int i = 0; i < fns.size() ; ++i)
if ( fns[i] )
fns[i]( a );
}