Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't std::tuple be element-wise constructed with a std::tuple of compatible types?

I can't initialize std::tuple elements element-wise from a std::tuple of compatible types. Why doesn't it work as with boost::tuple?

#include <tuple>
#include <boost/tuple/tuple.hpp>

template <typename T>
struct Foo
{
    // error: cannot convert 'std::tuple<int>' to 'int' in initialization
    template <typename U>
    Foo(U &&u) : val(std::forward<U>(u)) {}

    T val;
};

int main()
{
    boost::tuple<Foo<int>>{boost::tuple<int>{}};    // ok

    auto a = boost::tuple<int>{};
    boost::tuple<Foo<int>>{a};                      // ok

    std::tuple<Foo<int>>{std::tuple<int>{}};        // fails with rvalue

    auto b = std::tuple<int>{};
    std::tuple<Foo<int>>{b};                        // fails with lvalue
}

Live on Coliru (GCC or Clang and libstdc++ does not compile, however Clang and libc++ compiles without errors)


std::tuple is not doing element-wise construction and it instantiates Foo<int>::Foo<std::tuple<int>> instead of Foo<int>::Foo<int>. I thought std::tuple::tuple overloads no. 4 and 5 were exactly for that purpose:

template <class... UTypes>
tuple(const tuple<UTypes...>& other);

template <class... UTypes>
tuple(tuple<UTypes...>&& other);

Note:

Does not participate in overload resolution unless
std::is_constructible<Ti, const Ui&>::value is true for all i.

std::is_constructible<Foo<int>, int>::value is true. From the GCC template error, I can see that overload no. 3:

template <class... UTypes>
explicit tuple(UTypes&&... args);

is selected instead. Why?

like image 945
LogicStuff Avatar asked Jul 09 '16 12:07

LogicStuff


People also ask

Is std :: tuple a container?

The new std::array and std::tuple containers provide developers with additional ways to manage structured data efficiently.

What is a std :: tuple?

Class template std::tuple is a fixed-size collection of heterogeneous values. It is a generalization of std::pair. If std::is_trivially_destructible<Ti>::value is true for every Ti in Types , the destructor of tuple is trivial.

Is std :: tuple slow?

It is fully expected that std::tuple will be slower than std::pair when not optimized, because it is more complicated object. A pair has exactly two members, so its methods are straightforward to define. But tuple has arbitrary number of members and the only way to iterate over template argument list is with recursion.

Is tuple mutable in C++?

The tuple itself isn't mutable (i.e. it doesn't have any methods that for changing its contents). Likewise, the string is immutable because strings don't have any mutating methods. The list object does have mutating methods, so it can be changed.


1 Answers

Overloads (4) and (5) are poorer matches than (3) when passed a tuple& : they are const& and && overloads, while (3) matches exactly through the magic of perfect forwarding.

(3) is valid because your Foo(U&&) constructor is overly greedy.

Add SFINAE checks to Foo(U&&) so that it fails to match when it fails to build:

template <class U,
  std::enable_if_t<std::is_convertible<U,int>{},int>* =nullptr
>
Foo(U &&u) : val(std::forward<U>(u)) {}

The rvalue case should, however, work or be ambiguous. Looking at the error log of your live example, the only error I see is with the lvalue one.

like image 75
Yakk - Adam Nevraumont Avatar answered Oct 31 '22 21:10

Yakk - Adam Nevraumont