如何从模板函数参数包中提取参数?

时间:2016-03-09 05:46:34

标签: c++ variadic-templates

我正在实现一个可变参数模板函数,因为我想调用最多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);
}

3 个答案:

答案 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);
}

Demo

答案 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
}