Почему аргументы конструктора шаблона класса не определяются автоматически? - PullRequest
13 голосов
/ 27 октября 2011

Рассмотрим следующий класс:

template<typename T1, typename T2>
class Pair
{
     public:
         T1 First;
         T2 Second;
         Pair(const T1 &First, const T2 &Second) : First(First), Second(Second) { }
}

В c ++ запрещено следующее:

auto p = Pair(10, 10);

Почему это не разрешено?типы могут быть полностью определены из вызова конструктора.
Я знаю, что для этого есть обходной путь, например:

template<typename T1, typename T2>
Pair<T1, T2> MakePair(const T1 &First, const T2 &Second)
{
    return Pair<T1, T2>(First, Second);
}

Но зачем это нужно?Почему компилятор не определяет тип по аргументам так же, как из шаблона функции?Вы можете сказать, что это потому, что стандарт не позволяет этого, так почему же стандарт не допускает этого?

Редактировать:
Для тех, кто говорит, что это пример, почему это не должно быть разрешено:

template<typename T1, typename T2>
class Pair
{
     public:
         T1 First;
         T2 Second;
         Pair(const T1 &First, const T2 &Second) : First(First), Second(Second) { }
         Pair(const T2 &Second, const T1 &First) : First(First), Second(Second) { }
};

auto p = Pair(10,1.0);

Я могу сделать это с перегрузкой шаблонов функций:

template<typename T1, typename T2>
Pair<T1, T2> MakePair(const T1 &First, const T2 &Second)
{
    return Pair<T1, T2>(First, Second);
}
template<typename T1, typename T2>
Pair<T1, T2> MakePair(const T2 &Second, const T1 &First)
{
    return Pair<T1, T2>(First, Second);
}

Почему это разрешено для функций, но не для классов?

Ответы [ 3 ]

10 голосов
/ 27 октября 2011

Вот что Бьярн Страуструп должен сказать по этому вопросу:

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

5 голосов
/ 27 октября 2011

Рассмотрим следующее.

template<typename T1, typename T2>
class Pair
{
     public:
         T1 First;
         T2 Second;
         Pair(const T1 &First, const T2 &Second) : First(First), Second(Second) { }
         Pair(const T2 &Second, const T1 &First) : First(First), Second(Second) { }
};

auto p = Pair(10,1.0);

Должно ли p быть Pair<int,double> или Pair<double,int>?

2 голосов
/ 28 октября 2011

Больше не лучше вообще. Когда вам нужно спроектировать систему, вы должны быть осторожны с тем, как взаимодействуют различные функции, и должны выбрать самое простое и простое решение, которое предоставляет больше всего пользователям. Типичные вопросы начинаются с: что будет предлагать функция пользователям , какая стоимость потребует функция и , какие проблемы может принести функция .

В этом случае предоставление вывода типа помогает в том, что вы можете пропустить предоставление типа при объявлении переменной или при непосредственном вызове конструктора:

pair p = pair( 10, 20 );

Объявление переменной можно сделать с помощью auto, так что это больше не проблема, но это могло быть мотивирующей причиной для этой функции. В случае вызова конструктора добавленное значение весьма ограничено, вы можете предоставить именованный конструктор , как в стандартной библиотеке:

auto p = make_pair( 10, 20 );

Итак, в конце концов, добавленная стоимость возможности прямого вызова конструктора фактически ограничена, так как вы всегда можете предоставить именованную функцию, которая создаст объект для вас. Обратите внимание, что потенциальные дополнительные копии оптимизируются, что означает, что при вызове make_pair не требуется никаких дополнительных затрат по сравнению с прямым вызовом пары конструктора (10,20)

Теперь сосредоточимся на негативных последствиях, которые эта функция может иметь в пользовательском коде. Эта функция может быть использована только в некоторых случаях, когда нет конкурирующих перегрузок конструктора, так же, как она находит наилучшую перегрузку для шаблона функции, но, что более важно, специализации. Компилятор должен будет найти все потенциальные специализации шаблона класса и определить набор конструкторов, который предлагает каждый из них, а затем он должен будет разрешить наилучшую перегрузку конструктора и шаблон класса, в который он включен.

Оглядываясь назад на конкретный пример, который вы предоставили, для std::pair нет специализаций, поэтому мы находимся в простом случае, и все же приведенный выше пример кода потерпит неудачу:

pair p = pair( 10, 20 );

Почему? Ну, это на самом деле проблема, эта функция может привести пользователей в замешательство. Как я уже упоминал, правая часть выражения может быть легко разрешена с простым разрешением перегрузки (при условии, что специализаций не существует), и это вывело бы аргументы int, оба из них. Таким образом, первый шаг выведет выражение:

pair p = pair<int,int>(10,20);

Теперь это эквивалентно pair p( pair<int,int>( 10, 20 ) ), и нам нужен второй шаг разрешения, и на этом этапе компилятор увидит, что существует шаблонный конструктор:

template <typename first_type, typename second_type>
struct pair {
   first_type first;
   second_type second;

   template <typename U, typename V>
   pair( U f, V s ) : first(f), second(s) {}
};

Это означает, что каждое потенциальное создание экземпляра std::pair<T,U> имеет идеальное соответствие для этого вызова конструктора, и компилятор не может ни выбрать, ни предоставить какое-либо разумное сообщение об ошибке, кроме , неоднозначно слишком много способов .

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

Тот факт, что вы должны предоставить типы, снимает сложность у пользователя: нет необходимости думать, сможет ли компилятор определить правильный тип для меня. Отсутствие необходимости сосредотачиваться на этих деталях дает вам больше времени, чтобы подумать о реальной проблеме, которую вы хотите решить.

Иногда меньше - это лучше .

Кстати, auto решает это самым тривиальным способом: ему не нужно находить конструктор , который будет сопоставлять и угадывать тип из него, а просто использовать тип правой руки выражение-стороны, которое намного проще: работает только с одним объектом и использует этот тип для нового объекта. И даже тогда, с очень упрощенным вариантом использования, он обсуждался взад и вперед до тех пор, пока не было согласовано решение, с деталями, являющимися тем, можно ли использовать вычет для auto для создания ссылок или const или volatile будет частью выведенного типа ...

Просто мысль: без использования компилятора, какой тип выражения std::min( 10, 5.0 )?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...