12

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?

LogicStuff
  • 19,397
  • 6
  • 54
  • 74
  • Okay, it does not work with `-std=libstdc++`, but works with `-std=libc++` on Clang. Must be an implementation issue. – LogicStuff Jul 09 '16 at 12:45
  • 2
    Please file a bug in gcc's bugzilla. – Marc Glisse Jul 09 '16 at 12:45
  • 3
    This question will still give valuable information for those who will run into same problem. It will tell them that problem is with standard library implementation and not in their code. There are many common dupe targets which are explanation of the particular implementation bug (MinGW and `stoi` for example) – Revolver_Ocelot Jul 09 '16 at 13:18

1 Answers1

3

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.

LogicStuff
  • 19,397
  • 6
  • 54
  • 74
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Wow, that works! (I changed it to `typename = std::enable_if_t::value>`) Why is there a difference between libstdc++ and libc++? Is this an implementation detail, not specified by the standard? Should we report it and to whom? – LogicStuff Jul 09 '16 at 13:31
  • 1
    For the matching issue: `const auto b` still reproduces. You need to report this to libstdc++ (i.e. gcc's bugzilla). In addition, you may want to ask on the isocpp.org forum about adding a constructor that takes a non-const tuple lvalue, so it is a better match. – Marc Glisse Jul 10 '16 at 10:44
  • 1
    @logic thanks for fixing the typos, but the `class=` solution is worse than the `int*=nullptr` one. In the case of multiple overloads, `class=` fails, while `int*=` does not. – Yakk - Adam Nevraumont Jul 10 '16 at 13:19
  • @Yakk Mainly I wanted to involve `T` instead of `int`. Does it have to yield `int*` in this case? Can't it be `void*`? – LogicStuff Jul 10 '16 at 17:13
  • @Yakk And the rvalue case also produces an error, it's there. – LogicStuff Jul 10 '16 at 18:28
  • 1
    @LogicStuff `void*` is not a legal template non-type parameter type under the standard. `int*` is. We always set it to `nullptr`, it only exists for SFINAE reasons, and it is a better SFINAE system than the `class=` pattern. The `int` could be *any non-void type*, it just happens that C++ bans `void*` in this particular case. Some compilers fail to enforce that ban, but I believe in writing standard-compliant code when the cost is 4 extra characters. Again, that `int` type is *never used*. – Yakk - Adam Nevraumont Jul 10 '16 at 18:51
  • This answer is not clear enough IMHO. @Yakk, can you please edit the question to denote which items are (1), (2)... (5) respectively? – einpoklum Mar 19 '17 at 14:33
  • @ein references to the OP's linked cppreference page. Uncertain of legality if copy paste repeating here; but should be edited into question IMHO. – Yakk - Adam Nevraumont Mar 19 '17 at 15:59