22

It's actually a general question about interface design, but it's easier for me to just take std::pair as an example:

template <class T1, class T2> struct pair { ... pair(const T1& x, const T2& y); template<class U, class V> pair(U&& x, V&& y); ... }; 

So we can see there are two overloads that both take 2 arguments to initialize the 2 members of the pair. My question is, what is the benefit of providing the first one while the second one is available? Are there any types of arguments that can only be passed to the first one but not the second one?

(Let's just put aside the standard library's consideration for backward compatibility for a while, and discuss the interface design as a general question.)

5
  • 1
    It's possible for some types to be movable, but not copyable. On the other hand, it's also possible for some types to be copyable, but not movable. The two constructors allow for pair to be used with both types. Commented Mar 4, 2014 at 7:41
  • 5
    @Cornstalks The second constructor can take care of both cases, it uses universal references. See isocpp.org/blog/2012/11/… Commented Mar 4, 2014 at 7:49
  • Have a look at this short example, which shows that the second variant is called in all usual cases. Still, I believe there are rare cases where calling the second overload is not possible. But it's an interesting question. Commented Mar 4, 2014 at 8:02
  • My best guess is that this is a "backward compatibility" issue. One would simply not expect functions to be removed from a class in a later standard, even if a new function supersedes it. Commented Mar 4, 2014 at 8:48
  • @DanielKO: Thanks, I've bookmarked that page for reading. I'd delete my (misleading) comment, but I'll leave it for the sake of context. (also, I swear I tried to leave this comment yesterday but I don't see it here; strange...) Commented Mar 5, 2014 at 5:08

1 Answer 1

17

Sample implementation

template<typename T1, typename T2> struct simple_pair { simple_pair (T1 const& v1, T2 const& v2) // (1) : first (v1) , second (v2) { } template<class U, class V> simple_pair (U&& v1, V&& v2) // (2) : first (std::forward<U> (v1)) , second (std::forward<V> (v2)) { } T1 first; T2 second; }; 

Even though it might seem superfluous to provide both overload (1) and (2) there are cases where the second isn't usable, and the first one is not only preferred but actually required.

Consider that we'd like to construct some or both of our values while passing them to the constructor of simple_pair, without the first overload we would explicitly have to specify at least one of the types a second time.

T val; simple_pair<T, U> p1 { {}, {} }; // only (1) is applicable simple_pair<T, U> p2 { val, {} }; // only (1) is applicable simple_pair<T, U> p3 { T {}, U {} }; // can use (1) and (2), but this require a lot of typing 

Alternative implementation

If we instead had an implemenation using something as the below we could get around the "superfluous" overloads since the compiler would then know what type we'd like to construct in cases where such information is required.

template<typename T1, typename T2> struct simple_pair { template<class U = T1, class V = T2> simple_pair (U&& v1, V&& v2) : first (std::forward<U> (v1)) , second (std::forward<V> (v2)) { } T1 first; T2 second; }; 

 T val; simple_pair<T, U> p1 { {}, {} }; // legal simple_pair<T, U> p2 { val, {} }; // legal simple_pair<T, U> p3 { T {}, U {} }; // legal 

Why isn't std::pair stated to be implemented using the alternative implementation?

We can only guess, but presumably it's because of backward compatibility and the fact that specifying it the way it currently stands ease1 implementation for library implementors.

By having two separate overload one can easily disable the template<class U, class V> simple_pair (U&&, V&&) overload by conditionally adding it using macros (to see if we are using c++11 (or later)), instead of conditionally opting it out and adding another one.


Further potential reasons

  • Removing something from the standard is always a delicate thing to do.. following the better safe than sorry idiom; "if it doesn't hurt, leave it in." - @PlasmaHH

  • Everyone knows that the more lines of code you write, the better programmer you are.. and the better programmer you are; the more you get payed.


1. surely not by much, but heck.. it doesn't hurt being a bit pedantic.. ;-)

Sign up to request clarification or add additional context in comments.

2 Comments

Nice catch! This is a valid case. Thanks!
Some irrelevant thoughts: Does this mean that using T{} as the initializer instead of {} is better because it enables move construction in this case? (Though copy and move construction doesn't have much difference in performance for a value-initialized object (probably carrying very few data).

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.