1324. Still too many implicit conversions for pair and tuple

Section: 23.4.2 [pairs.pair], 23.5.3.1 [tuple.cnstr] Status: Resolved Submitter: Daniel Krügler Opened: 2010-03-20 Last modified: 2016-02-10

Priority: Not Prioritized

View all other issues in [pairs.pair].

View all issues with Resolved status.

Discussion:

In analogy to library defect 811, tuple's variadic constructor

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

creates the same problem as pair:

#include <tuple>

int main()
{
  std::tuple<char*> p(0);
}

produces a similar compile error for a recent gcc implementation.

I suggest to follow the same resolution path as has been applied to pair's corresponding c'tor, that is require that these c'tors should not participate in overload resolution, if the arguments are not implicitly convertible to the element types.

Further-on both pair and tuple provide converting constructors from different pairs/tuples that should be not available, if the corresponding element types are not implicitly convertible. It seems astonishing that in the following example

struct A {
  explicit A(int);
};

A  a = 1; // Error

std::tuple<A> ta = std::make_tuple(1); // # OK?

the initialization marked with # could be well-formed.

[ Only constraints on constructors are suggested. Adding similar constraints on assignment operators is considered as QoI, because the assigments wouldn't be well-formed anyway. ]

  1. Following 23.4.2 [pairs.pair]/5 add a new Remarks element:

    template<class U, class V> pair(const pair<U, V>& p);
    

    5 Effects: Initializes members from the corresponding members of the argument, performing implicit conversions as needed.

    Remarks: This constructor shall not participate in overload resolution unless U is implicitly convertible to first_type and V is implicitly convertible to second_type.

  2. Following 23.4.2 [pairs.pair]/6 add a new Remarks element:

    template<class U, class V> pair(pair<U, V>&& p);
    

    6 Effects: The constructor initializes first with std::move(p.first) and second with std::move(p.second).

    Remarks: This constructor shall not participate in overload resolution unless U is implicitly convertible to first_type and V is implicitly convertible to second_type.

  3. Following 23.5.3.1 [tuple.cnstr]/7 add a new Remarks element:

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

    6 Requires: Each type in Types shall satisfy the requirements of MoveConstructible (Table 33) from the corresponding type in UTypes. sizeof...(Types) == sizeof...(UTypes).

    7 Effects: Initializes the elements in the tuple with the corresponding value in std::forward<UTypes>(u).

    Remarks: This constructor shall not participate in overload resolution unless each type in UTypes is implicitly convertible to its corresponding type in Types.

  4. Following 23.5.3.1 [tuple.cnstr]/13 add a new Remarks element:

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

    12 Requires: Each type in Types shall be constructible from the corresponding type in UTypes. sizeof...(Types) == sizeof...(UTypes).

    13 Effects: Constructs each element of *this with the corresponding element of u.

    Remarks: This constructor shall not participate in overload resolution unless each type in UTypes is implicitly convertible to its corresponding type in Types.

    14 [Note: enable_if can be used to make the converting constructor and assignment operator exist only in the cases where the source and target have the same number of elements. — end note]

  5. Following 23.5.3.1 [tuple.cnstr]/16 add a new Remarks element:

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

    15 Requires: Each type in Types shall shall satisfy the requirements of MoveConstructible (Table 33) from the corresponding type in UTypes. sizeof...(Types) == sizeof...(UTypes).

    16 Effects: Move-constructs each element of *this with the corresponding element of u.

    Remarks: This constructor shall not participate in overload resolution unless each type in UTypes is implicitly convertible to its corresponding type in Types.

    [Note: enable_if can be used to make the converting constructor and assignment operator exist only in the cases where the source and target have the same number of elements. — end note]

  6. Following 23.5.3.1 [tuple.cnstr]/18 add a new Remarks element:

    template <class U1, class U2> tuple(const pair<U1, U2>& u);
    

    17 Requires: The first type in Types shall be constructible from U1 and the second type in Types shall be constructible from U2. sizeof...(Types) == 2.

    18 Effects: Constructs the first element with u.first and the second element with u.second.

    Remarks: This constructor shall not participate in overload resolution unless U1 is implicitly convertible to the first type in Types and U2 is implicitly convertible to the second type in Types.

  7. Following 23.5.3.1 [tuple.cnstr]/20 add a new Remarks element:

    template <class U1, class U2> tuple(pair<U1, U2>&& u);
    

    19 Requires: The first type in Types shall shall satisfy the requirements of MoveConstructible(Table 33) from U1 and the second type in Types shall be move-constructible from U2. sizeof...(Types) == 2.

    20 Effects: Constructs the first element with std::move(u.first) and the second element with std::move(u.second)

    Remarks: This constructor shall not participate in overload resolution unless U1 is implicitly convertible to the first type in Types and U2 is implicitly convertible to the second type in Types.

[ 2010-10-24 Daniel adds: ]

Accepting n3140 would solve this issue.

Proposed resolution:

See n3140.