This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of C++17 status.

### 2549. Tuple EXPLICIT constructor templates that take `tuple` parameters end up taking references to temporaries and will create dangling references

Section: 22.4.4.1 [tuple.cnstr] Status: C++17 Submitter: Ville Voutilainen Opened: 2015-10-11 Last modified: 2017-07-30

Priority: 2

View other active issues in [tuple.cnstr].

View all other issues in [tuple.cnstr].

View all issues with C++17 status.

Discussion:

Consider this example:

>     #include <utility>
>     #include <tuple>

>     struct X {
>       int state; // this has to be here to not allow tuple
>                  // to inherit from an empty X.
>       X() {
>       }

>       X(X const&) {
>       }

>       X(X&&) {
>       }

>       ~X() {
>       }
>     };

>     int main()
>     {
>       X v;
>       std::tuple<X> t1{v};
>       std::tuple<std::tuple<X>&&> t2{std::move(t1)}; // #1
>       std::tuple<std::tuple<X>> t3{std::move(t2)}; // #2
>     }

The line marked with `#1` will use the constructor `template <class... UTypes> EXPLICIT constexpr tuple(tuple<UTypes...>&& u);` and will construct a temporary and bind an rvalue reference to it. The line marked with `#2` will move from a dangling reference.

In order to solve the problem, the constructor templates taking tuples as parameters need additional SFINAE conditions that SFINAE those constructor templates away when `Types...` is constructible or convertible from the incoming tuple type and `sizeof...(Types)` equals one. libstdc++ already has this fix applied.

There is an additional check that needs to be done, in order to avoid infinite meta-recursion during overload resolution, for a case where the element type is itself constructible from the target tuple. An example illustrating that problem is as follows:

>     #include <tuple>

>     struct A
>     {
>       template <typename T>
>       A(T)
>       {
>       }

>       A(const A&) = default;
>       A(A&&) = default;
>       A& operator=(const A&) = default;
>       A& operator=(A&&) = default;
>       ~A() = default;
>     };

>     int main()
>     {
>       auto x = A{7};
>       std::make_tuple(x);
>     }

I provide two proposed resolutions, one that merely has a note encouraging trait-based implementations to avoid infinite meta-recursion, and a second one that avoids it normatively (implementations can still do things differently under the as-if rule, so we are not necessarily overspecifying how to do it.)

[2016-02-17, Ville comments]

It was pointed out at gcc bug 69853 that the fix for LWG 2549 is a breaking change. That is, it breaks code that expects constructors inherited from `tuple` to provide an implicit base-to-derived conversion. I think that's just additional motivation to apply the fix; that conversion is very much undesirable. The example I wrote into the bug report is just one example of a very subtle temporary being created.

Previous resolution from Ville [SUPERSEDED]:

> 1. Alternative 1:

>    1. In 22.4.4.1 [tuple.cnstr]/17, edit as follows:

>       >     template <class... UTypes> EXPLICIT constexpr tuple(const tuple<UTypes...>& u);

>       > > […]

>       > > -17- Remarks: This constructor shall not participate in overload resolution unless

>       > > - `is_constructible<Ti, const Ui&>::value` is true for all `i`., and

>       > > - `sizeof...(Types) != 1`, or `is_convertible<const tuple<UTypes...>&, Types...>::value` is false and `is_constructible<Types..., const tuple<UTypes...>&>::value` is false.

>       > > [Note: to avoid infinite template recursion in a trait-based implementation for the case where `UTypes...` is a single type that has a constructor template that accepts `tuple<Types...>`, implementations need to additionally check that `remove_cv_t<remove_reference_t<const tuple<UTypes...>&>>` and `tuple<Types...>` are not the same type. — end note]

>       > > The constructor is explicit if and only if `is_convertible<const Ui&, Ti>::value` is false for at least one `i`.

>    2. In 22.4.4.1 [tuple.cnstr]/20, edit as follows:

>       >     template <class... UTypes> EXPLICIT constexpr tuple(tuple<UTypes...>&& u);

>       > > […]

>       > > -20- Remarks: This constructor shall not participate in overload resolution unless

>       > > - `is_constructible<Ti, Ui&&>::value` is true for all `i`., and

>       > > - `sizeof...(Types) != 1`, or `is_convertible<tuple<UTypes...>&&, Types...>::value` is false and `is_constructible<Types..., tuple<UTypes...>&&>::value` is false.

>       > > [Note: to avoid infinite template recursion in a trait-based implementation for the case where `UTypes...` is a single type that has a constructor template that accepts `tuple<Types...>`, implementations need to additionally check that `remove_cv_t<remove_reference_t<tuple<UTypes...>&&>>` and `tuple<Types...>` are not the same type. — end note]

>       > > The constructor is explicit if and only if `is_convertible<Ui&&, Ti>::value` is false for at least one `i`.

> 2. Alternative 2 (do the sameness-check normatively):

>    1. In 22.4.4.1 [tuple.cnstr]/17, edit as follows:

>       >     template <class... UTypes> EXPLICIT constexpr tuple(const tuple<UTypes...>& u);

>       > > […]

>       > > -17- Remarks: This constructor shall not participate in overload resolution unless

>       > > - `is_constructible<Ti, const Ui&>::value` is true for all `i`., and

>       > > - `sizeof...(Types) != 1`, or `is_convertible<const tuple<UTypes...>&, Types...>::value` is false and `is_constructible<Types..., const tuple<UTypes...>&>::value` is false and `is_same<tuple<Types...>, remove_cv_t<remove_reference_t<const tuple<UTypes...>&>>>::value` is false.

>       > > The constructor is explicit if and only if `is_convertible<const Ui&, Ti>::value` is false for at least one `i`.

>    2. In 22.4.4.1 [tuple.cnstr]/20, edit as follows:

>       >     template <class... UTypes> EXPLICIT constexpr tuple(tuple<UTypes...>&& u);

>       > > […]

>       > > -20- Remarks: This constructor shall not participate in overload resolution unless

>       > > - `is_constructible<Ti, Ui&&>::value` is true for all `i`., and

>       > > - `sizeof...(Types) != 1`, or `is_convertible<tuple<UTypes...>&&, Types...>::value` is false and `is_constructible<Types..., tuple<UTypes...>&&>::value` is false and `is_same<tuple<Types...>, remove_cv_t<remove_reference_t<tuple<UTypes...>&&>>>::value` is false.

>       > > The constructor is explicit if and only if `is_convertible<Ui&&, Ti>::value` is false for at least one `i`.

[2016-03, Jacksonville]

STL provides a simplification of Ville's alternative #2 (with no semantic changes), and it's shipping in VS 2015 Update 2.

Proposed resolution:

This wording is relative to N4567.

1. This approach is orthogonal to the Proposed Resolution for LWG 2312:

   1. Edit 22.4.4.1 [tuple.cnstr] as indicated:

      >     template <class... UTypes> EXPLICIT constexpr tuple(const tuple<UTypes...>& u);

      > > […]

      > > -17- Remarks: This constructor shall not participate in overload resolution unless

      > > - `is_constructible<Ti, const Ui&>::value` is `true` for all `i`, and

      > > - `sizeof...(Types) != 1`, or (when `Types...` expands to `T` and `UTypes...` expands to `U`) `!is_convertible_v<const tuple<U>&, T> && !is_constructible_v<T, const tuple<U>&> && !is_same_v<T, U>` is `true`.

      > > The constructor is explicit if and only if `is_convertible<const Ui&, Ti>::value` is `false` for at least one `i`.

      >     template <class... UTypes> EXPLICIT constexpr tuple(tuple<UTypes...>&& u);

      > > […]

      > > -20- Remarks: This constructor shall not participate in overload resolution unless

      > > - `is_constructible<Ti, Ui&&>::value` is `true` for all `i`, and

      > > - `sizeof...(Types) != 1`, or (when `Types...` expands to `T` and `UTypes...` expands to `U`) `!is_convertible_v<tuple<U>, T> && !is_constructible_v<T, tuple<U>> && !is_same_v<T, U>` is `true`.

      > > The constructor is explicit if and only if `is_convertible<Ui&&, Ti>::value` is `false` for at least one `i`.

2. This approach is presented as a merge with the parts of the Proposed Resolution for LWG 2312 with overlapping modifications in the same paragraph, to provide editorial guidance if 2312 would be accepted.

   1. Edit 22.4.4.1 [tuple.cnstr] as indicated:

      >     template <class... UTypes> EXPLICIT constexpr tuple(const tuple<UTypes...>& u);

      > > […]

      > > -17- Remarks: This constructor shall not participate in overload resolution unless

      > > - `sizeof...(Types) == sizeof...(UTypes)`, and

      > > - `is_constructible<Ti, const Ui&>::value` is `true` for all `i`, and

      > > - `sizeof...(Types) != 1`, or (when `Types...` expands to `T` and `UTypes...` expands to `U`) `!is_convertible_v<const tuple<U>&, T> && !is_constructible_v<T, const tuple<U>&> && !is_same_v<T, U>` is `true`.

      > > The constructor is explicit if and only if `is_convertible<const Ui&, Ti>::value` is `false` for at least one `i`.

      >     template <class... UTypes> EXPLICIT constexpr tuple(tuple<UTypes...>&& u);

      > > […]

      > > -20- Remarks: This constructor shall not participate in overload resolution unless

      > > - `sizeof...(Types) == sizeof...(UTypes)`, and

      > > - `is_constructible<Ti, Ui&&>::value` is `true` for all `i`, and

      > > - `sizeof...(Types) != 1`, or (when `Types...` expands to `T` and `UTypes...` expands to `U`) `!is_convertible_v<tuple<U>, T> && !is_constructible_v<T, tuple<U>> && !is_same_v<T, U>` is `true`.

      > > The constructor is explicit if and only if `is_convertible<Ui&&, Ti>::value` is `false` for at least one `i`.