模板和重载分辨率

templates and overload resolution

本文关键字:分辨率 重载      更新时间:2023-10-16

我正在阅读"C++模板,完整指南"一书,在第22章中,它通过一个std::function-linke类的例子介绍了类型擦除的概念:

#include "functorwrapper.hpp"
// primary template (declaration)
template <typename Signature>
class Function;
// partial class template specialization
template <typename ReturnType, typename... Args>
class Function<ReturnType(Args...)>
{
public:
// constructors
Function() : mFunctorWrapper(nullptr) {}                 // default constructor
Function(const Function &);                              // copy constructor
Function(Function &&);                                   // move constructor
template <typename Functor> Function(Functor &&);        // generalized constructor
// destructor
~Function() { delete mFunctorWrapper; }
// copy/move operations
Function &operator=(Function const &);                          // copy assignmaent operator
Function &operator=(Function &&);                               // move assignment operator 
template <typename Functor> Function &operator=(Functor &&);    // generalized assignment operator
// overloaded function call operator
ReturnType operator()(Args...);
private:
FunctorWrapperBase<ReturnType(Args...)> *mFunctorWrapper;
};
template <typename ReturnType, typename... Args>
Function<ReturnType(Args...)>::Function(const Function &other) : mFunctorWrapper(nullptr)
{
if (other.mFunctorWrapper)
mFunctorWrapper = other.mFunctorWrapper->Clone();
}
template <typename ReturnType, typename... Args>
Function<ReturnType(Args...)>::Function(Function &&other) :  mFunctorWrapper(other.mFunctorWrapper)
{
other.mFunctorWrapper = nullptr;
}
template <typename ReturnType, typename... Args>
template <typename Functor>
Function<ReturnType(Args...)>::Function(Functor &&functor)
{
// remove reference if l-value (template type argument deduced as Functor &)
mFunctorWrapper = new FunctorWrapper<typename std::remove_reference<Functor>::type, ReturnType(Args...)>(std::forward<Functor>(functor));
}
template <typename ReturnType, typename... Args>
Function<ReturnType(Args...)> &Function<ReturnType(Args...)>::operator=(const Function &other)
{
mFunctorWrapper = other.mFunctorWrapper->Clone();
return *this;
}
template <typename ReturnType, typename... Args>
Function<ReturnType(Args...)> &Function<ReturnType(Args...)>::operator=(Function &&other)
{
mFunctorWrapper = other.mFunctorWrapper;
other.mFunctorWrapper = nullptr;
return *this;
}
template <typename ReturnType, typename... Args>
template <typename Functor>
Function<ReturnType(Args...)> &Function<ReturnType(Args...)>::operator=(Functor &&functor)
{
mFunctorWrapper = new FunctorWrapper<typename std::remove_reference<Functor>::type, ReturnType(Args...)>(std::forward<Functor>(functor));
}
template <typename ReturnType, typename... Args>
ReturnType Function<ReturnType(Args...)>::operator()(Args... args)
{
mFunctorWrapper->Invoke(args...);
}

此类仅管理为类型为 FunctorWrapper 的对象分配的内存,FunctorWrapper 是表示不同类型的函子(或可调用对象)的类模板。

如果我从函数对象、lambda 或指向函数的指针构造函数类型的对象,一切都很顺利(我可以调用对象并调用相对函数)。

但是,如果我尝试从另一个函数复制构造(或移动构造)函数,编译器仅将调用绑定到接受任意对象的构造函数(具有模板参数 Functor 和通用引用作为函数参数的通用构造函数),从而导致崩溃。

我认为如果我像这样调用构造函数:

Function<void(double)> fp4(&FreeFunction);
fp4(1.2);
Function<void(double)> fp5 = fp4;  // copy construction

应该调用复制构造函数,因为它更专用。 我遵循书上的例子,但我一定做错了什么。

我认为这是书中的一个缺陷。

应该调用复制构造函数,因为它更专业

template <typename Functor> Function(Functor &&);是更好的匹配。

typename Functor被推导为Function<...> &后,构造函数变成Function(Function &);,这比Function(const Function &);传递一个非常量对象更好的匹配。

您可以使用 SFINAE 解决此问题:

template
<
typename Functor,
typename = std::enable_if_t<!std::is_same_v<Function,
std::remove_cv_t<std::remove_reference_t<Functor>>>>
>
Function(Functor &&);

您需要对赋值运算符执行相同的操作。或者,您可以简单地删除它(分配函子应该仍然有效,因为编译器应该调用Function(Functor &&)然后调用移动赋值)。

是的,不幸的是,转发引用版本比复制构造函数匹配得更好,后者需要隐式转换为对 const的引用。

您可以将约束设置为

template <typename Functor, std::enable_if_t<!std::is_base_of_v<Function, std::decay_t<Functor>>>* = nullptr> 
Function(Functor &&);  

PS:我用std::is_base_of代替std::is_same,因为Function可能是继承的,在派生类的复制构造函数的实现中,Function的复制构造函数可能会用派生类类型的参数调用。