Richard
Richard

Reputation: 27

How to extract params from template function parameter pack?

I am implementing a variadic template function because I want to make calls with up to 7 params. My calls go like this.

foo(1, 2, "msg", 4, 5.0);  

or

foo(3, 4.1, "msg");  

The first parameter identifies a protocol to use, and every parameter thereafter is what I would like to place in struct Msg.

struct Msg {
    int proto;
    string str;
    int a;
    int b;
    double d;
};

The biggest problem I'm having is, I don't know how to get the remaining parameters after the first and store them. I'd like to use the first param to tell which struct members are to be populated. The part that confuses me is that each recursive call changes the function 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);
}

Upvotes: 2

Views: 723

Answers (3)

prestokeys
prestokeys

Reputation: 4849

Here's a generalization that might be of use to you. Here the arguments passed can be in any order (even empty). The compiler will deduce which arguments are assigned to which data members of the object in question (if at all).

#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
}

Upvotes: 0

Serge Ballesta
Serge Ballesta

Reputation: 148965

If I have correctly understood your problem, you want to build a variadic template function for which:

  • first parameter is a protocol number
  • following parameters can be of different types and aliment a struct

So I assume that you already have defined one struct Msg (depending on T ?) and that you are able to write the function that aliments the struct with one single param using the proto from the struct:

template <typename Arg>
void process(struct Msg& msg, Arg value) {
    switch(msg.proto) {
    case PROTO_A:
        ...
    }
}

(maybe process(struct Msg<T>& msg, ...) but I leave that for you because you did not say how you process the individual parameters...

You can then write a recursive process version:

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...);
    }
}

And your foo function can become:

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

Upvotes: 0

Jarod42
Jarod42

Reputation: 217348

You may use it without recursion:

Your specific fill method, with un-call fallback (using SFINAE and priority):

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");
}

And then your dispatcher 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

Upvotes: 1

Related Questions