Подтвердить что ты не робот

Интерфейс конструктора std:: pair

На самом деле это общий вопрос о дизайне интерфейса, но мне проще взять std::pair в качестве примера:

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

Итак, мы можем видеть, что есть две перегрузки, которые принимают два аргумента для инициализации двух членов пары. Мой вопрос: в чем преимущество первого, а второго? Существуют ли какие-либо типы аргументов, которые могут быть переданы только первой, но не второй?

(Позвольте просто отложить рассмотрение стандартной библиотеки для обратной совместимости на некоторое время и обсудить дизайн интерфейса как общий вопрос.)

4b9b3361

Ответ 1

Пример реализации

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;
};

Несмотря на то, что может показаться излишним предоставить как перегрузку (1), так и (2), есть случаи, когда вторая не используется, а первая не только предпочтительна, но и фактически необходима.

Считаем, что мы хотели бы построить некоторые или оба наших значения, передавая их конструктору simple_pair, без первой перегрузки нам явно нужно было бы указать по крайней мере один из типов второй раз.

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

Альтернативная реализация

Если бы вместо этого было реализовано что-то, как показано ниже, мы могли бы обойти "лишние" перегрузки, поскольку компилятор тогда знал бы, какой тип мы хотели бы построить в тех случаях, когда такая информация требуется.

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

Почему не объявлено std::pair с использованием альтернативной реализации?

Мы можем только догадываться, но, по-видимому, это из-за обратной совместимости и того факта, что указывать ее так, как она в настоящее время упрощает реализацию 1 для разработчиков библиотек.

С помощью двух отдельных перегрузок можно легко отключить перегрузку template<class U, class V> simple_pair (U&&, V&&), условно добавив ее с помощью макросов (чтобы убедиться, что мы используем c++11 (или позже)) вместо условного выбора и добавления другого.


Другие потенциальные причины

  • Удаление чего-то из стандарта - это всегда деликатная вещь, следуя лучшей безопасной, чем жалкая идиома; "если это не повредит, оставьте его". - @PlasmaHH

  • Всем известно, что чем больше строк кода вы пишете, тем лучше программист вы и лучший программист вы; тем больше вы получаете оплату.


1. наверняка не много, но, черт возьми.. это не больно быть немного педантичным. ;-)