Недопустимый вывод аргумента шаблона класса с производным классом

#include <utility>

template<class T1, class T2>
struct mypair : std::pair<T1, T2>
{ using std::pair<T1, T2>::pair; };

int main()
{
    (void)std::pair(2, 3); // It works
    (void)mypair(2, 3);    // It doesn't work
}

Является ли это хорошо сформированным?

Можно ли выводить аргументы шаблона класса во втором случае, если конструкторы наследуются? Являются ли конструкторы std::pair участвующими в создании неявных указателей вычитания для mypair?

Мой компилятор - g++ 7.2.0.

Ответ 1

Я думаю, что это ошибка gcc (или, по крайней мере, незначительный дефект формулировки основного языка).

Унаследованные конструкторы считаются конструкторами в соответствии с [namespace.udecl]/16:

Аналогично, конструкторы, которые вводятся с помощью объявления-объявления, рассматриваются как конструкторы производного класса при поиске конструкторов производного класса ([class.qual]) или формирования набора кандидатов перегрузки ([ over.match.ctor], [over.match.copy], [over.match.list])

Этот список условий технически не включает [over.match.class.deduct], но импликация заключается в том, что конструкторы базового класса являются конструкторами производного класса. И правило в [over.match.class.deduct] должно учитывать:

Если C определен, для каждого конструктора C, шаблон функции со следующими свойствами [...]

Мы ищем конструкторы производного класса, только не в любом из перечисленных случаев. Но этот пример должен работать концептуально:

template <class T> struct base { base(T ) { } };
template <class T> struct derived : base<T> { using base<T>::base; };

base b = 4;    // ok
derived d = 4; // error

Ответ 2

Краткая история: в стандарте нет правила, в котором говорится, как это будет работать, ни правила, в котором говорится, что он не работает. Таким образом, GCC и Clang консервативно отклоняют, а не изобретают (нестандартное) правило.

Длинная история: mypair pair базовый класс является зависимым типом, поэтому поиск его конструкторов не может быть успешным. Для каждой специализации mytype<T1, T2> соответствующие конструкторы pair<T1, T2> являются конструкторами mytype, но это не правило, которое может быть осмысленно применено к шаблону до создания экземпляра вообще.

В принципе, может существовать правило, в котором говорится, что вы смотрите на конструкторы основного шаблона pair в этой ситуации (так же, как мы это делаем при поиске конструкторов самого mypair для вывода аргумента шаблона шаблона) но в настоящее время такого стандарта не существует в стандарте. Такое правило быстро падает, однако, когда базовый класс становится более сложным:

template<typename T> struct my_pair2 : std::pair<T, T> {
  using pair::pair;
};

Какие конструкторы должны быть введены здесь условно? И в таких случаях я думаю, что разумно ясно, что этот поиск не может работать:

template<typename T> struct my_pair3 : arbitrary_metafunction<T>::type {
  using arbitrary_metafunction<T>::type::type;
};

Возможно, мы получим изменение правила, чтобы позволить вычет через ваши my_pair и my_pair2 выше, если/когда мы получаем правила вывода аргументов шаблона шаблона шаблона для шаблонов псевдонимов:

template<typename T> using my_pair3 = std::pair<T, T>;
my_pair3 mp3 = {1, 2};

Сложности, связанные с этим, во многом такие же, как и в случае с наследованным конструктором. Фейсал Вали (один из других разработчиков аргумента шаблона аргументов класса) имеет конкретный план, как заставить такие случаи работать, но комитет С++ еще не обсуждал это расширение.