mercredi 31 août 2016

Workaround for passing variadic arguments to lambda expression if compiler does not support it

I have two solutions, both work as I expect but the first one works only with newer version of GCC (5.2.1 for sure) and the second one works in GCC >= 4.8.3. The problem is that at my work I unfortunately do not have access to newer version of GCC.

The first solution is basically what I wanted by the behavior and by the level of complexity. The second one also works but this is the ugliest thing I have ever written and to be honest I do not know if this is the correct code (but it works).

Ok, firstly I will describe the problem that I am facing: I have a function called logic() here, which takes callable object to invoke some additional logic among other instructions. These instructions of course are not here, because they are not necessary, they are marked as (X).

template <typename C>
void logic (C c) {
    // before callable actions (X)
    c ();
    // after callable actions (X)
}

What I want to do as the callable object is to set some bool value(s) using appropriate type(s) - set_single() function. For the example I will use some additional types:

struct P1 {};
struct P2 {};
struct P3 {};

Consider first solution:

namespace Solution1 {

template <typename T>
void set_single (bool c) {
    // use type T to set c value - details are not the case here.

    std::cout << "value = " << c << std::endl;
    std::cout << "P1 = " << std::is_same<T, P1>::value << std::endl;
    std::cout << "P2 = " << std::is_same<T, P2>::value << std::endl;
    std::cout << "P3 = " << std::is_same<T, P3>::value << std::endl;
}

template <typename T, typename K, typename... Ts, typename... Vs>
void set_single (bool t, bool k, Vs&&... args) {
    set_single<T> (t);
    set_single<K, Ts...> (k, std::forward<Vs> (args)...);
}

template <typename... Ts, typename... Args>
void set (Args&&... args) {
    static_assert (sizeof... (Ts) == sizeof... (Args), "");

    logic ([&] () {
        set_single<Ts...> (std::forward<Args> (args)...);
    });
}

}

set() function is a wrapper and it is public for the user, set_single() are implementation details so they are hidden (for simplicity they are not written in a class).

So, passing callable object to logic() function inside set() function I invoke set_single() function arbitrary number of times passing all the values with corresponding types that I need. So, for example, usage could be like this:

Solution1::set<P1, P2, P3> (true, false, true);

and this will use type P1 to set true, type P2 to set false and type P3 to set true.

So, here we have a solution when your compiler does not support passing variadic arguments to lambda expression.

namespace Solution2 {

template <typename... Ts>
struct X {
    X () = default;
    X (Ts... t) : tup {std::make_tuple (t...)} {}
    std::tuple<Ts...> tup;
};

template <int...>
struct Ints {};

template <int N, int... Is>
struct Int_seq : Int_seq<N-1, N, Is...> {};

template <int... Is>
struct Int_seq<0, Is...> {
    using type = Ints<0, Is...>;
};

template <int... Is>
using Iseq = typename Int_seq<Is...>::type;

template <int I, typename... Args, typename... Types>
void set_single (Ints<I>, const std::tuple<Args...>& a, const std::tuple<Types...>& t) {
    std::cout << "value = " << std::get<I> (a) << std::endl;
    auto p1 = std::get<I> (t);
    auto p2 = std::get<I> (t);
    auto p3 = std::get<I> (t);
    std::cout << "P1 = " << std::is_same<P1, decltype (p1)>::value << std::endl;
    std::cout << "P2 = " << std::is_same<P2, decltype (p2)>::value << std::endl;
    std::cout << "P3 = " << std::is_same<P3, decltype (p3)>::value << std::endl;
}

template <int I, int K, int... Is, typename... Args, typename... Types>
void set_single (Ints<I, K, Is...>, const std::tuple<Args...>& a, const std::tuple<Types...>& t) {
    set_single (Ints<I> {}, a, t);
    set_single (Ints<K, Is...> {}, a, t);
}

template <typename... Ts, typename... Args>
void set (Args... args) {
    static_assert (sizeof... (Ts) == sizeof... (Args), "");

    X<Ts...> types {};
    X<Args...> arguments {args...};

    logic ([&types, &arguments] () {
        set_single (Iseq<std::tuple_size<decltype (arguments.tup)>::value-1> {}, arguments.tup, types.tup);
    });

}

}

I used type Solution2::X to store values and types. I tried to do some std::bind() stuff, but it was painful, so I used some integer sequence (which probably implementation is rather poor). Like I said, both solutions work but the second one is not so cool like the first one.

Could any of you tell me if Solution2 can be replaced by some kind of easier solution? I am pretty sure that some exists.

And usage with output looks like:

Solution1::set<P1, P2, P3> (true, false, true);

value = 1
P1 = 1
P2 = 0
P3 = 0
value = 0
P1 = 0
P2 = 1
P3 = 0
value = 1
P1 = 0
P2 = 0
P3 = 1


Solution2::set<P1, P2, P3> (true, false, true);

value = 1
P1 = 1
P2 = 0
P3 = 0
value = 0
P1 = 0
P2 = 1
P3 = 0
value = 1
P1 = 0
P2 = 0
P3 = 1

Aucun commentaire:

Enregistrer un commentaire