std::tuple duplicate type T in get<T>(tuple) - Compile time assertion failure

I am storing the variadic arguments to an object constructor inside a std::tuple and so far so good. But when calling an object function using the stored arguments and std::get<>(), I will be thrown a compile-time assertion failure that I simply don't understand. This will happen only when all arguments are not each of a different type.

The compiler error message is:

msvc\14.16.27023\include\tuple(934): error C2338: duplicate type T in get(tuple)

mcve below:

#include <tuple>
#include <iostream>

using namespace std;

template<class... Args>
struct store_in_tuple {

    tuple<Args...> m_tuple_args;

    store_in_tuple(Args... args) : m_tuple_args{ args... } {}

    void func() {
        func_tuple(std::get<Args>(m_tuple_args)...);
    }

    void func_tuple(Args... args) {}
};

int main(int argc, char** argv) {

    store_in_tuple<int, float, double, int> sit1(1, 2.0f, 3.0, 4);
    sit1.func(); // <- not ok

    store_in_tuple<int, float, double, size_t> sit2(1, 2.0f, 3.0, 4);
    sit2.func(); // <- ok

    return 0;
}

Why does this happen and is there a workaround ?

3 answers

  • answered 2019-07-10 22:02 Fureeish

    The example can be simlpified to the following:

    auto t = std::make_tuple(1, 's', 2);
    std::get<int>(t);
    

    here, we have a t of a type std::tuple<int, char, int>. std::get can also work with types (alongside indexes), unless you have a duplicate type. std::get<char> will work since there is only one char in the t, but std::get<int> will not work, since it does not know which int to fetch - the 1 or the 2?

    This is what's happening here:

    void func() {
        func_tuple(std::get<Args>(m_tuple_args)...);
    }
    

    the std::get<Args>, after expansion, will not work if Args... contain at least one duplicate type, because it will simply not know which one to fetch.

  • answered 2019-07-10 22:03 Deduplicator

    Use C++17 std::apply() to pass all the elements of a tuple to a function.

    std::apply([&](auto... x){ func_tuple(x...); }, m_tuple_args);
    

    You insist on staying with C++14?
    No problem, cppreference.com shows a short and simple production-quality example-implementation.

    Alternatively, you can work directly with std::make_index_sequence to get unique indices instead of duplicated types.

  • answered 2019-07-10 22:47 max66

    Why does this happen [?]

    All goes well when the Args... types are all differents.

    You get an error when types collides.

    This is because std::get<T>(tuple_val), where T is a type, "Fails to compile unless the tuple has exactly one element of that type" (as you can read in this page). And this seems reasonable to me.

    So all goes well with

    store_in_tuple<int, float, double, size_t> 
    

    because all types are differents, and you get an error from

    store_in_tuple<int, float, double, int>
    

    because the two calls to std::get<int>(m_tuple_args) fail.

    and is there a workaround ?

    Use the numeric version of std::get(), that is ever available, also when types collides.

    The usual way in C++14 pass through an helper function with std::index_sequence and std::make_index_sequence (or std::index_sequence_for).

    Seems complicated but it's very simple

    template <std::size_t ... Is>
    void func_helper (std::index_sequence<Is...> const)
     { func_tuple(std::get<Is>(m_tuple_args)...); }
    
    void func ()
     { func_helper(std::index_sequence_for<Args...>{}); }
    

    If you can use C++17, you can use std::apply() that (I suppose) use std::index_sequence under the hood.