为什么选择默认模板函数比其专用版本更匹配?

Why is a default template function picked as a better match than its specialized version?

我有以下模板成员函数:

template <class ParameterT>
typename boost::enable_if_c<boost::is_base_of<BaseOne, ParameterT>::value
                            || boost::is_base_of<BaseTwo, ParameterT>::value, void>::type
MyClass::doSomething(const boost::shared_ptr<ParameterT> &param);

调用doSomething(sharedPtrTo_derivedFromBaseOne)doSomething(sharedPtrTo_derivedFromBaseTwo)doSomething(sharedPtrTo_derivedFromBaseOneAndBaseTwo)都可以,用任何其他参数调用都不行,这确实是我目前想要的。

现在在上面的实现中,我还想要两个调用:doSomething_baseOnedoSomething_baseTwo。显然,它们应该针对到目前为止进行的所有调用进行编译(因此我的 shared_ptr 参数是 BaseOne 派生或 BaseTwo 派生,或两者兼而有之)。我想到了这样的事情:

template<class BaseOneDerived>
void doSomething_baseOne(const boost::shared_ptr<BaseOneDerived> &param);
{
    std::cout << "doing nothing BaseOne";
}

template<class BaseTwoDerived>
void doSomething_baseTwo(const boost::shared_ptr<BaseTwoDerived> &param)
{
    std::cout << "doing nothing BaseTwo";
}

//doSomething implementation:
{
    doSomething_baseOne(param);
    doSomething_baseTwo(param);
}

以及 doSomething_baseOnedoSomething_baseTwo 专长:

template<>
void MyClass::doSomething_baseOne(const boost::shared_ptr<BaseOne> &param)
{
    std::cout << "doing something with BaseOne";
}

template<>
void MyClass::doSomething_baseTwo(const boost::shared_ptr<BaseTwo> &param)
{
    std::cout << "doing something with BaseTwo";
}

现在假设我有这个简单的层次结构:

class A : public BaseOne {};

class B : public BaseTwo {};

class C : public BaseOne, public BaseTwo {};

我愿意接听以下电话:

MyClass X;
X.doSomething(boost::shared_ptr(new A());
X.doSomething(boost::shared_ptr(new B());
X.doSomething(boost::shared_ptr(new C());

接收以下输出:

//for first call
doing something with BaseOne
doing nothing BaseTwo

//for second call
doing nothing BaseOne
doing something with BaseTwo

//for third call
doing something with BaseOne
doing something with BaseTwo

相反,我收到了 6 次 "doing nothing with" 消息(实际上我还没有实现非专用版本,所以我真的只是收到了 link 次未定义的引用,但是你明白了)。

所以基本上编译器会选择默认的 doSomething_baseOnedoSomething_baseTwo 作为比专用版本更好的匹配。这是为什么?我怎样才能克服这个问题来实现我想要的?

这是因为编译器实例化了以下模板:

template<>
void doSomething_baseOne(const boost::shared_ptr<A> &param)
{
std::cout << "doing nothing BaseOne";
}
template<>
void doSomething_baseTwo(const boost::shared_ptr<A> &param)
{
std::cout << "doing nothing BaseOne";
}
template<>
void doSomething_baseOne(const boost::shared_ptr<B> &param)
{
std::cout << "doing nothing BaseOne";
}
template<>
void doSomething_baseTwo(const boost::shared_ptr<B> &param)
{
std::cout << "doing nothing BaseOne";
}
template<>
void doSomething_baseOne(const boost::shared_ptr<C> &param)
{
std::cout << "doing nothing BaseOne";
}
template<>
void doSomething_baseTwo(const boost::shared_ptr<C> &param)
{
std::cout << "doing nothing BaseOne";
}

哪些模板专业化比您提供的更好。

这样做,使用 enable_if 技巧:

template<class T>
std::enable_if_t<std::is_base_of<T,BaseOne>::value>
doSomething_baseOne(const boost::shared_ptr<T>& param){
   std::cout << "doing something baseOne";
}
template<class T>
std::enable_if_t<!std::is_base_of<T,BaseOne>::value>
doSomething_baseOne(const boost::shared_ptr<T>& param){
   std::cout << "doing nothing baseOne";
}
template<class T>
std::enable_if_t<std::is_base_of<T,BaseTwo>::value>
doSomething_baseTwo(const boost::shared_ptr<T>& param){
   std::cout << "doing something baseTwo";
}
template<class T>
std::enable_if_t<!std::is_base_of<T,BaseTwo>::value>
doSomething_baseTwo(const boost::shared_ptr<T>& param){
   std::cout << "doing nothing baseTwo";
}

或者这样,使用标签调度:

template<class T>
doSomething_baseOne(const boost::shared_ptr<T>& param
      ,std::integral_constant<bool,true>){
   std::cout << "doing something baseOne";
}
template<class T>
doSomething_baseOne(const boost::shared_ptr<T>& param
      ,std::integral_constant<bool,false>){
   std::cout << "doing nothing baseOne";
}
template<class T>
doSomething_baseOne(const boost::shared_ptr<T>& param){
   doSmething_baseOne(param,
         std::integral_constant<
               bool,std::is_base_of<T,BaseOne>::value
                               >{});
}
template<class T>
doSomething_baseTwo(const boost::shared_ptr<T>& param
      ,std::integral_constant<bool,true>){
   std::cout << "doing something baseTwo";
}
template<class T>
doSomething_baseTwo(const boost::shared_ptr<T>& param
      ,std::integral_constant<bool,false>){
   std::cout << "doing nothing baseTwo";
}
template<class T>
doSomething_baseTwo(const boost::shared_ptr<T>& param){
   doSmething_baseTwo(param,
         std::integral_constant<
               bool,std::is_base_of<T,BaseTwo>::value
                               >{});
}

更好的解决办法是使用概念。甚至更干净,constexpr if (C++17).