Определяемые пользователем и автоматические шаблоны выводов руководств по приоритетам

Допустим, у нас есть такой класс с определяемым пользователем руководством по дедукции:

template<typename T, typename... Args>
struct Foo
{
    Foo(Args&&...) { std::cout << "just Args: " << __PRETTY_FUNCTION__ << std::endl; }
    Foo(Args&&..., T&&) { std::cout << "Args and T: " << __PRETTY_FUNCTION__ << std::endl; }
};

template<typename... Args>
Foo(Args&&...) -> Foo<Void, Args...>;

Теперь попробуем создать экземпляр этого класса: Foo foo { 10 };. Какими будут выведенные аргументы шаблона и какой конструктор будет вызываться?

После некоторых экспериментов выясняется, что это зависит от компилятора. А именно, gcc 7 и clang 6 (из ствола), похоже, выбирают автоматическое руководство, создавая экземпляр T с int и Args с пустым пакетом, поэтому вывод

Args and T: Foo<T, Args>::Foo(Args&& ..., T&&) [with T = int; Args = {}]

clang 5, с другой стороны, выбирает определяемое пользователем руководство:

just Args: Foo<Void, int>::Foo(Args &&...) [T = Void, Args = <int>]

Какой выбор правильный и как в этом случае можно использовать определяемое пользователем руководство по дедукции?

Полный пример доступен на wandbox.


person 0xd34df00d    schedule 17.12.2017    source источник


Ответы (1)


Начнем с первых принципов. Попытка сделать вывод из Foo{10} включает в себя разрешение перегрузки на этом наборе:

template <typename T, typename... Args>
Foo<T, Args...> __f(Args&&... ); // ctor #1

template <typename T, typename... Args>
Foo<T, Args...> __f(Args&&..., T&&); // ctor #2

template <typename... Args>
Foo<Void, Args...> __f(Args&&... ); // deduction guide

В функции, синтезированной из первого конструктора, T - это невыведенный контекст. В функции, синтезированной из второго конструктора, Args является невыведенным контекстом. Так что ни то, ни другое не жизнеспособно. Руководство по дедукции жизнеспособно, так что это тривиально лучший жизнеспособный кандидат, поэтому мы получаем Foo<Void, int>.

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

Любое другое поведение является ошибкой компилятора (зарегистрировано 83447).

person Barry    schedule 17.12.2017
comment
Почему Args является невыведенным контекстом в функции, синтезированной из ctor # 2? Похоже, это полностью выводимо: в данном конкретном случае пустая пачка. - person 0xd34df00d; 17.12.2017
comment
Правильно, я привел простой пример, который близок к тому, что вы показали в отчете об ошибке, и он отлично скомпилировался со всеми gcc, которые я пробовал, и обоими clang 5 и 6, поэтому я предположил, что это правильно. Ссылка, которую вы дали, доказывает, что это не так! Интересно, что clang ‹4 отклоняет код. На данный момент, чтобы код работал с существующими компиляторами, я добавил параметр фиктивного тега ко второму ctor, поскольку этот ctor в любом случае является деталью реализации и не должен вызываться пользователями класса. Большое спасибо! - person 0xd34df00d; 17.12.2017