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

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

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

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

В С++ не допускается следующее:

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

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

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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>?

Ответ 3

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

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

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> имеет идеальное соответствие для этого вызова конструктора, и компилятор не может выбрать и не предоставлять никакого разумного сообщения об ошибке, отличного от двусмысленного, слишком многими способами.

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

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

Иногда лучше.

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

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