Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::pair constructor interface

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.)

like image 975
goodbyeera Avatar asked Mar 04 '14 07:03

goodbyeera


1 Answers

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.. ;-)

like image 119
Filip Roséen - refp Avatar answered Oct 27 '22 02:10

Filip Roséen - refp