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

Может ли шаблон конструктора вызывать неоднозначность в параметре С++ 17 для параметра шаблона класса

Рассмотрим простой пример:

template <class T>
struct foo {
    template <template <class> class TT>
    foo(TT<T>&&) {}
    foo(foo<T>&&){}
    foo() {}
};

int main() {
    foo      f1(foo<int>{}); //case 1.
    foo<int> f2(foo<int>{}); //case 2.
}

Случай 1. вызывает неоднозначность в аргументе шаблона дедукции класса foo в clang, но не в gcc. Я думал, что функции шаблона (здесь - конструктор) имеют более низкий приоритет при разрешении перегрузки. Это не так?

Сообщение об ошибке:

prog.cc:10:14: error: ambiguous deduction for template arguments of 'foo'
    foo      f1(foo<int>{}); //case 1.
             ^
prog.cc:4:5: note: candidate function [with T = int, TT = foo]
    foo(TT<T>&&) {}
    ^
prog.cc:5:5: note: candidate function [with T = int]
    foo(foo<T>&&){}
    ^
1 error generated.

[clang demo] [gcc demo]

4b9b3361

Ответ 1

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

Чтобы процитировать [over.match.funcs]/1:

Подпункты [over.match.funcs] описывают набор кандидатов функций и списка аргументов, представленных для разрешения перегрузки в каждый из семи контекстов, в которых используется разрешение перегрузки.исходные преобразования и конструкции, определенные в этих подпунктах предназначены только для описания разрешения перегрузки обработать. Реализация не требуется для использования таких преобразований и конструкций.

Это ясно говорит о том, что процесс разрешения перегрузки всегда один и тот же. Единственное отличие состоит в том, как формируется набор кандидатов.

И как указано [over.match.class.deduct]/1

Формируется набор функций и шаблонов функций, включающий:

  • Для каждого конструктора шаблона первичного класса, назначенного именем шаблона, если шаблон определен, шаблон функции с следующие свойства:

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

    • Типы параметров функции - это параметры конструктора.

    • Тип возврата - это спецификация шаблона класса, обозначенная аргументами шаблона-шаблона и шаблона, соответствующими шаблону параметры, полученные из шаблона класса.

Каждый c'tor вводит псевдофункцию в набор кандидатов. Вот так:

template <class T>                           foo(foo<T>&&) -> foo<T> 
template <class T, template<class> class TT> foo(TT<T>&&) -> foo<T> 

Чтобы проиллюстрировать далее, если это была свободная функция bar:

template <template <class> class TT, class T>
void bar(TT<T>&&) {}

template <class T>
void bar(foo<T>&&){}

Тогда порядок функций шаблона помещает первую перегрузку ниже второй.

Ответ 2

У них нет более низкого приоритета. Вы можете решить проблему, используя SFINEA. Это описано в "Эффективном современном С++" от Скотта Майера.

template <class T>
struct foo {
    template <template <class> class TT, class =  std::enable_if_t<!std::is_same_v<foo<T>, std::decay_t<TT<T>>>>>
    foo(TT<T>&&) {}
    foo(foo<T>&&){}
    foo() {}
};