类any中的模板构造函数与非模板构造函数

时间:2014-09-14 17:40:53

标签: c++

想象一下写boost::any之类的东西:

class any {
public:
  any();
  any(const any &);
  any(any &&);
  template<typename ValueType> any(const ValueType &);
  template<typename ValueType> any(ValueType &&);

是否会为任何可能的any调用适当的(复制/移动)构造函数?或者是否必须使用SFINAE编写,例如像这样:

template<typename ValueType,
  typename = typename std::enable_if<
    !std::is_same<any, typename std::decay<ValueType>::type>::value
  >::type>
  any(const ValueType& value)
template<typename ValueType,
  typename = typename std::enable_if<
    !std::is_same<any, typename std::decay<ValueType>::type>::value
  >::type>
  any(ValueType&& value)

问题是:我是否需要保护模板化构造函数(从某个值构造any)或者我可以保留它,因为非模板(复制/移动)构造函数将始终匹配any?如果可能的话,volatile修饰符或某些奇怪的std::move((const any&)it)呢?

对描述搜索构造函数的答案将是最受关注的,谢谢。

编辑:构建包含另一个any的{​​{1}}将是一个问题,我确定要避免这种情况(SFINAE确保不会发生这种情况)。

2 个答案:

答案 0 :(得分:7)

使用C ++ 11和 Universal Reference (以及带有此类参数的构造函数)的引入,重载决策的规则将选择模板化版本。

事实是,如果编译器可以在模板化和非模板化函数之间进行选择,那么它将与非模板一起使用。但只有当它们同样好的时候才会这样做

  

§13.3.3最佳可行功能[over.match.best]

     
      
  1. [...]鉴于这些定义,可行函数F1被定义为比另一个可行函数更好的函数   F2如果对于所有参数 i ,则ICS i (F1)不是比ICS 转换序列> i (F2),然后

         

    - 对于某些参数 j ,ICS j (F1)是更好的转换序列而不是ICS < sub> j (F2),或如果不是

         

    [...]

         

    - F1是非模板函数,F2是函数模板特化,[...]

  2.   

那就是说,有两个构造函数声明如下:

any(const any &);

template <typename ValueType>
any(const ValueType &);

编译器将选择非模板化版本,因为实例化模板化的版本会与完全相同的声明产生。

然而,随着构造函数采用 Unviersal Reference ,情况发生了根本性的变化:

any(const any &);

template <typename ValueType>
any(ValueType &&);

在使用常规直接初始化语法复制实例的上下文中:

any a;
any b{a};

a的评估类型是左值any & ,不含 const修饰符。在生成用于重载解析的候选构造函数集之后,编译器最终得到以下签名:

any(const any &); // non-template

any(any &); // instantiated template

然后:

  

§13.3.1候选函数和参数列表[over.match.funcs]

     
      
  1. 在候选者是函数模板的每种情况下,使用模板参数推导(14.8.3,14.8.2)生成候选函数模板特化。 然后以通常的方式将这些候选项作为候选函数处理。给定名称可以引用一个或多个函数模板,也可以引用一组重载的非模板函数。在这种情况下,从每个功能模板生成的候选函数与一组非模板候选函数组合。
  2.   

也就是说,模板版本是一个更好的匹配,这是编译器选择的。

然而,如果有人:

const any a; // const!
any b{a};

然后这次从构造函数生成的 Universal Reference 生成的构造函数签名与copy-constructor的非模板版本相同,所以只有非模板版本被调用。

  

如果有可能,那么volatile修饰符或某些奇怪的std :: move((const any&amp;))怎么样?

完全相同的情况发生。 通用参考构造函数更匹配。

即,std::move((const any&)it)评估const any &&类型的表达式。

非模板移动构造函数的参数可以采用非const rvalue引用(因此根本不匹配,因为它缺少const修饰符)。

非模板复制构造函数的参数可以采用 const左值引用(很好,const rvalue可以通过const lvalue引用绑定,但不是完全匹配)。

然后,采用通用参考的实例化模板再次将是一个更好的匹配将被调用。

答案 1 :(得分:3)

作为一般规则,如果模板和非模板函数在其他方面同样匹配,则选择非模板版本而不是模板版本。由于any复制/移动构造函数是非模板的,因此对于rvalues或常量左值,它们优先于模板构造函数。

然而由于rvalue参考模板的特殊规则,template<typename ValueType> any(ValueType &&);的推断类型将是any&,这是更好匹配。因此,在复制非常量左值时,您将调用模板化构造函数。

因此,对于该模板化构造函数,您需要一个SFINAE规则,但对于带有左值引用的模板化构造函数则不需要。