我有几个模板函数,其模板参数总是属于同一个枚举,并且希望根据该枚举中变量的运行时值来调用各种实例:
#include <iostream>
enum Number {One, Two};
template<Number N>
void g(int m) {
std::cout << N << " " << m << std::endl;
}
int main() {
auto n = Number::One;
int m = 0;
// I want to use this sort of construct for many different functions g
if (n == Number::One) {
g<Number::One>(m);
}
else if (n == Number::Two) {
g<Number::Two>(m);
}
}
我不是每次都写分支,而是想重构它 - 这是我目前的尝试:
struct h {
template<Number N>
void operator()(int m) const {
g<N>(m);
}
};
template <typename F, typename... Args>
void split(F&& f, Number n, Args&&... args) {
if (n == Number::One) {
f.template operator()<Number::One>(args...);
}
else if (n == Number::Two) {
f.template operator()<Number::Two>(args...);
}
}
int main() {
split(h{}, n, m);
}
这是实现这一目标的最简洁方法吗?特别是,是否可以重写split
以使其直接接受g
,而不需要包含结构h
?
答案 0 :(得分:3)
你必须将你的函数包装在某些东西中,但我们可以使用lambda而不是类。
如果您的枚举值是连续的,请使用:
(下面的代码依赖于连续的枚举值。它允许我们使用函数指针数组进行快速调度。如果你的枚举不是连续的,请阅读下面的内容。)
#include <iostream>
#include <utility>
enum Number {One, Two, NumberCount};
template <Number N> void g(int m)
{
std::cout << N << " " << m << std::endl;
}
template <typename L, std::size_t ...I>
const auto &split_impl(std::index_sequence<I...>, L lambda)
{
static decltype(lambda(std::integral_constant<Number, One>{})) array[] =
{lambda(std::integral_constant<Number, Number(I)>{})...};
return array;
}
template <typename L, typename ...P> void split(L lambda, Number n, P &&... p)
{
split_impl(std::make_index_sequence<NumberCount>{}, lambda)[n](std::forward<P>(p)...);
}
int main()
{
auto wrapped_g = [](auto i){return g<i.value>;};
split(wrapped_g, One, 42);
}
如果您的枚举值不连续,请使用:
#include <iostream>
#include <utility>
enum Number {One, Two};
template <Number N> void g(int m)
{
std::cout << N << " " << m << std::endl;
}
template <Number ...I, typename L> auto split_impl(L lambda, Number n)
{
decltype(lambda(std::integral_constant<Number, One>{})) ret = 0;
((I == n ? (ret = lambda(std::integral_constant<Number, Number(I)>{}), 0) : 1) && ...);
return ret;
}
template <typename L, typename ...P> void split(L lambda, Number n, P &&... p)
{
split_impl<One, Two>(lambda, n)(std::forward<P>(p)...);
// ^~~~~~~~
// List all your enum values here
}
int main()
{
auto wrapped_g = [](auto i){return g<i.value>;};
split(wrapped_g, One, 42);
}
P.S。为简单起见,我使用了std::size_t
和std::index_sequence
。如果您需要健壮性,则应使用std::integral_sequence<std::underlying_type_t<MyEnum>>
。
答案 1 :(得分:2)
从第一个例子
#include <iostream>
enum Number {One, Two};
template<Number N>
void g(int m) {
std::cout << N << " " << m << std::endl;
}
int main() {
auto n = Number::One;
int m = 0;
// I want to use this sort of construct for many different functions g
if (n == Number::One) {
g<Number::One>(m);
}
else if (n == Number::Two) {
g<Number::Two>(m);
}
}
我假设您基本上希望根据运行时值g<N>
调度到模板特化n
。所以基本上你想要用手写的if-else
- 级联或带有一些集中调度逻辑的开关来代替。
The following code遍历枚举值并调用与函数参数g<N>
匹配的函数m
。它仅在枚举是连续的并且定义迭代的结束值时才起作用。这至少集中了调度,如果枚举被修改,调度将自动运行。
#include <functional>
#include <iostream>
enum class Number {One, Two, Three, MAX};
template<Number N>
void g(int m) {
std::cout << static_cast<int>(N) << " " << m << std::endl;
}
template<Number N>
struct Dispatch {
void call(Number n, int m) {
if (N == n) {
g<N>(m);
} else if (N != Number::MAX) {
Dispatch< static_cast<Number>(static_cast<int>(N)+1) > next;
next.call(n, m);
}
}
};
template<>
struct Dispatch<Number::MAX> {
void call(Number, int) {
throw "Ohje";
}
};
void dispatch(Number n, int m) {
Dispatch<Number::One> d;
d.call(n, m);
}
int main(int argc, char* argv[]) {
std::cout << argc << std::endl;
dispatch(static_cast<Number>(argc-2), 42);
return 0;
}
You can further generalize the code to use it with different functions for g
.我将函数调用包装到helper结构中以表示自由函数g<N>
。如果N
和m
与`d
#include <functional>
#include <iostream>
enum class Number {One, Two, Three, MAX};
template<Number N>
void g(int m) {
std::cout << "g" << static_cast<int>(N) << " " << m << std::endl;
}
template<Number N> struct S {
void operator()(int m) {
std::cout << "S" << std::endl;
g<N>(m);
}
};
template<Number N> struct G {
void operator()(int m) {
std::cout << "G" << std::endl;
g<N>(m);
}
};
template<template<Number> typename C, Number N>
struct Dispatch {
void call(Number n, int m) {
if (N == n) {
C<N> c;
c(m);
} else if (N != Number::MAX) {
Dispatch< C, static_cast<Number>(static_cast<int>(N)+1) > next;
next.call(n,m);
}
}
};
template<template<Number> typename C>
struct Dispatch<C, Number::MAX> {
void call(Number, int) {
throw "Ohje";
}
};
template<template<Number> typename C>
void dispatch(Number n, int m) {
Dispatch<C, Number::One> d;
d.call(n,m);
}
int main(int argc, char* argv[]) {
std::cout << argc << std::endl;
dispatch<S>(static_cast<Number>(argc-2), 42);
dispatch<G>(static_cast<Number>(argc-2), 23);
return 0;
}
答案 2 :(得分:1)
对g
的定义方式进行一些修改是可能的。
特别是,我认为在类型系统中表示模板函数是不可能的,但是可以表示模板类/结构。所以,我将g
更改为具有静态成员函数的结构。
#include <iostream>
enum Number { One, Two };
template <Number N>
struct g
{
static void call(int m)
{
std::cout << N << " " << m << std::endl;
}
};
template <template<Number> class F, class R = void>
struct Wrap
{
template <class... Args>
static R call(Number n, Args&&... args)
{
switch (n) {
case One: return F<One>::call(std::forward<Args>(args)...);
case Two: return F<Two>::call(std::forward<Args>(args)...);
}
}
};
int main()
{
auto n = Number::One;
int m = 0;
Wrap<g>::call(n, m);
}