我正在实现一个可变参数模板函数,因为我想调用最多7个参数。我的电话是这样的。
foo(1, 2, "msg", 4, 5.0);
或
foo(3, 4.1, "msg");
第一个参数标识要使用的协议,之后的每个参数都是我想要放在struct Msg中的。
struct Msg {
int proto;
string str;
int a;
int b;
double d;
};
我遇到的最大问题是,我不知道如何在第一个之后获取剩余参数并存储它们。我想使用第一个参数来告诉要填充哪些结构成员。令我困惑的部分是每个递归调用都会改变函数sig。
template<typename T>
T bar(T t) {
cout << __PRETTY_FUNCTION__ << endl;
return t;
}
template<typename T, typename... Args>
void foo(T value, Args... args)
{
cout << __PRETTY_FUNCTION__ << endl;
Msg msg;
msg.proto = value;
switch (value) {
case PROTO_A:
// when calling 'foo(1, 2, "msg", 4, 5.0)'
// 1 is proto and placed in struct Msg (msg.proto = value)
// but how to get the remaining params from args into struct Msg
foo(args...);
break;
case PROTO_B:
foo(args...);
break;
default:
break;
}
send_msg(msg);
}
答案 0 :(得分:1)
您可以在没有递归的情况下使用它:
您的特定填充方法,具有取消回拨(使用SFINAE和优先级):
struct overload_priority_low {};
struct overload_priority_high : overload_priority_low {};
template<typename... Args>
auto Fill_ProtoA(Msg& msg, overload_priority_high, Args... args)
-> decltype(std::tie(msg.a, msg.str, msg.b, msg.d) = std::tie(args...), void())
{
std::tie(msg.a, msg.str, msg.b, msg.d) = std::tie(args...);
}
template<typename... Args> auto Fill_ProtoA(Msg& msg, overload_priority_low, Args... args)
{
throw std::runtime_error("Should not be called");
}
template<typename... Args>
auto Fill_ProtoB(Msg& msg, overload_priority_high, Args&&...args)
-> decltype(std::tie(msg.d, msg.str) = std::tie(args...), void())
{
std::tie(msg.d, msg.str) = std::tie(args...);
msg.a = 42;
msg.b = 42;
}
template<typename... Args> auto Fill_ProtoB(Msg& msg, overload_priority_low, Args... args)
{
throw std::runtime_error("Should not be called");
}
然后是您的调度员foo
:
template<typename T, typename... Args>
void foo(T value, Args... args)
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
Msg msg;
msg.proto = value;
switch (value) {
case PROTO_A: Fill_ProtoA(msg, overload_priority_high{}, args...); break;
case PROTO_B: Fill_ProtoB(msg, overload_priority_high{}, args...); break;
default: break;
}
send_msg(msg);
}
答案 1 :(得分:0)
如果我已正确理解您的问题,您需要构建一个可变参数模板函数:
所以我假设您已经定义了一个struct Msg
(取决于T?)并且您可以使用结构中的proto编写使用一个参数对结构进行混合的函数:
template <typename Arg>
void process(struct Msg& msg, Arg value) {
switch(msg.proto) {
case PROTO_A:
...
}
}
(也许是process(struct Msg<T>& msg, ...)
但是我留给你的是因为你没有说明你如何处理个别参数......
然后您可以编写递归过程版本:
template <typename First first, typename... Args>
void process(struct Msg& msg, First first, Args ... args) {
process(msg, first);
if (sizeof...(args) > 0) {
process(msg, args...);
}
}
你的foo功能可以变成:
template<typename T, typename... Args>
void foo(T value, Args... args)
{
cout << __PRETTY_FUNCTION__ << endl;
Msg msg;
msg.proto = value;
process(msg, args);
send_msg(msg);
}
答案 2 :(得分:0)
这是一个可能对您有用的概括。传递的参数可以是任何顺序(甚至是空的)。编译器将推断出哪些参数被分配给相关对象的哪些数据成员(如果有的话)。
#include <iostream>
#include <tuple>
#include <string>
constexpr std::size_t NOT_FOUND = -1;
using default_tuple = std::tuple<int, bool, double, char, std::string>;
template <typename, std::size_t> struct Pair;
template <typename Tuple, typename T, std::size_t Start, typename = void>
struct Find : std::integral_constant<std::size_t, NOT_FOUND> {};
template <typename Tuple, typename T, std::size_t Start>
struct Find<Tuple, T, Start, std::enable_if_t<(Start < std::tuple_size<Tuple>::value)>> {
static constexpr size_t value = std::is_same<std::remove_reference_t<T>, std::tuple_element_t<Start, Tuple>>::value ? Start : Find<Tuple, T, Start+1>::value;
};
template <typename T, typename... Pairs> struct SearchPairs;
template <typename T>
struct SearchPairs<T> : std::integral_constant<std::size_t, 0> {};
template <typename T, typename First, typename... Rest>
struct SearchPairs<T, First, Rest...> : SearchPairs<T, Rest...> {};
template <typename T, std::size_t I, typename... Rest>
struct SearchPairs<T, Pair<T,I>, Rest...> : std::integral_constant<std::size_t, I> {};
template <typename Tuple, typename ArgsTuple, std::size_t Start, std::size_t OriginalSize, typename Indices, typename LastIndices, typename = void>
struct ObtainIndices {
using type = Indices;
};
template <typename Tuple1, typename Tuple2, std::size_t Start, std::size_t OriginalSize, std::size_t... Is, typename... Pairs>
struct ObtainIndices<Tuple1, Tuple2, Start, OriginalSize, std::index_sequence<Is...>, std::tuple<Pairs...>,
std::enable_if_t<(Start < std::tuple_size<Tuple2>::value)>> {
using T = std::tuple_element_t<Start, Tuple2>;
static constexpr std::size_t start = SearchPairs<T, Pairs...>::value, // Searching through Pairs..., and will be 0 only if T is not found among the pairs. Else we start after where the last T was found in Tuple1.
index = Find<Tuple1, T, start>::value;
using type = std::conditional_t<(index < OriginalSize),
typename ObtainIndices<Tuple1, Tuple2, Start+1, OriginalSize, std::index_sequence<Is..., index>, std::tuple<Pair<T, index+1>, Pairs...>>::type, // Pair<T, index+1> because we start searching for T again (if ever) after position 'index'. Also, we must place Pair<T, index+1> before the Pairs... pack rather than after it because if a Pair with T already exists, that Pair must not be used again.
typename ObtainIndices<Tuple1, Tuple2, Start+1, OriginalSize, std::index_sequence<Is..., index>, std::tuple<Pair<T, index>, Pairs...>>::type // We add Pair<T, index> right before Pairs... since we want to use the default T value again if another T value is ever needed. Now this could clutter up the std::tuple<Pairs...> pack with many Pairs, causing more compile time, but there is no guarantee that Pair<T, index+1> is already there (since this default T value could be the first T value found).
>;
};
template <std::size_t I, std::size_t J, typename Tuple1, typename Tuple2>
void assignHelper (Tuple1&& tuple1, const Tuple2& tuple2) {
if (std::get<J>(tuple2) != std::tuple_element_t<J, Tuple2>()) // Make the assignment only if the right hand side is not the default value.
std::get<I>(std::forward<Tuple1>(tuple1)) = std::get<J>(tuple2);
}
template <typename Tuple1, typename Tuple2, std::size_t... Is, std::size_t... Js>
void assign (Tuple1&& tuple1, Tuple2&& tuple2, std::index_sequence<Is...>, std::index_sequence<Js...>) {
const int a[] = {(assignHelper<Is, Js>(tuple1, tuple2), 0)...};
static_cast<void>(a);
}
template <typename T, typename... Args>
void fillData (T& t, Args&&... args) {
auto s = t.tuple_ref();
std::tuple<Args...> a = std::tie(args...);
const auto tuple = std::tuple_cat(a, default_tuple{}); // Add default values for each type, in case they are needed if those types are absent in 'a'.
using IndexSequence = typename ObtainIndices<std::remove_const_t<decltype(tuple)>, decltype(s), 0, sizeof...(Args), std::index_sequence<>, std::tuple<>>::type;
assign (s, tuple, std::make_index_sequence<std::tuple_size<decltype(s)>::value>{}, IndexSequence{});
}
// Testing
class Thing {
int a, b;
char c;
double d;
std::string s;
int n;
public:
auto tuple_ref() {return std::tie(a, b, c, d, s, n);} // This (non-const) member function must be defined for any class that wants to be used in the 'fillData' function. Here 'auto' is std::tuple<int&, int&, char&, double&, std::string&, int&>.
void print() const {std::cout << "a = " << a << ", b = " << b << ", c = " << c << ", d = " << d << ", s = " << s << ", n = " << n << '\n';}
};
int main() {
Thing thing;
fillData (thing, 5, 12.8);
thing.print(); // a = 5, b = uninitialized, c = uninitialized, d = 12.8, s = uninitialized, n = uninitialized
fillData (thing, 3.14, 2, 'p', std::string("hi"), 5, 'k', 5.8, 10, std::string("bye"), 9); // Note that std::string("hi") must be use instead of "hi" because "hi" is of type const char[2], not std::string (then the program would not compile). See my thread http://stackoverflow.com/questions/36223914/stdstring-type-lost-in-tuple/36224017#36224006
thing.print(); // a = 2, b = 5, c = p, d = 3.14, s = hi, n = 10
fillData (thing, 4.8, 8, 'q', std::string("hello"));
thing.print(); // a = 8, b = 5, c = q, d = 4.8, s = hello, n = 10
fillData (thing, std::string("game over"));
thing.print(); // a = 8, b = 5, c = q, d = 4.8, s = game over, n = 10
}