class模板的c++17参数推导中构造函数模板会引起歧义吗

Can constructor template cause ambiguity in the c++17 parameter deduction of class template

考虑一个简单的例子:

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. 在 clang 中导致 foo class 的模板参数推导产生歧义,但在 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]

他们的优先级并不低。您可以使用 SFINEA 解决问题。这在 Scott Meyers 的 Effective Modern C++ 中有所描述。

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

这是一个 Clang 错误。候选集是由 c'tors 形成的事实应该是无关紧要的,因为在形成候选集之后,使用相同的规则来选择最佳重载来排序隐式转换序列和模板函数排序。

引用[over.match.funcs]/1

The subclauses of [over.match.funcs] describe the set of candidate functions and the argument list submitted to overload resolution in each of the seven contexts in which overload resolution is used. The source transformations and constructions defined in these subclauses are only for the purpose of describing the overload resolution process. An implementation is not required to use such transformations and constructions.

这清楚地表明重载解析过程始终是相同的。唯一的区别是候选集是如何形成的。

并由 [over.match.class.deduct]/1

指定

A set of functions and function templates is formed comprising:

  • For each constructor of the primary class template designated by the template-name, if the template is defined, a function template with the following properties:

    • The template parameters are the template parameters of the class template followed by the template parameters (including default template arguments) of the constructor, if any.

    • The types of the function parameters are those of the constructor.

    • The return type is the class template specialization designated by the template-name and template arguments corresponding to the template parameters obtained from the class template.

每个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>&&){}

然后模板函数排序会使第一个重载低于第二个重载。