构造函数模板可以在类模板的c ++ 17参数推导中导致歧义

时间:2017-09-05 11:46:28

标签: c++ templates language-lawyer c++17 template-deduction

考虑一个简单的例子:

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类的模板参数推导中导致模糊,但在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]

2 个答案:

答案 0 :(得分:11)

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

引用[over.match.funcs]/1

  

[over.match.funcs]的子条款描述了候选集   函数和提交到重载决策的参数列表   使用重载决策的七个上下文中的每一个。   这些子条款中定义的源转换和构造   仅用于描述重载决策的目的   处理。使用此类转换不需要实现   和结构。

这清楚地表明过载解决过程始终是相同的。唯一的区别是候选集的形成方式。

[over.match.class.deduct]/1

指定
  

形成一组功能和功能模板,包括:

     
      
  • 对于template-name指定的主类模板的每个构造函数,如果定义了模板,则为一个函数模板   以下属性:

         
        
    • 模板参数是类模板的模板参数,后跟模板参数(包括默认值)   构造函数的模板参数,如果有的话。

    •   
    • 函数参数的类型是构造函数的类型。

    •   
    • 返回类型是由模板名称和模板对应的模板指定的类模板特化   从类模板中获取的参数。

    •   
  •   

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

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

答案 1 :(得分:1)

他们没有较低的优先级。您可以使用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() {}
};