模板和重载决议
templates and overload resolution
我正在读 "C++ templates, complete guide" 一书,第 22 章以 std::function-linke class:[=12= 的示例介绍了类型擦除的概念]
#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...);
}
这个 class 只管理为 FunctorWrapper 类型的对象分配的内存,它是一个 class 模板,代表不同种类的函子(或可调用对象)。
如果我从一个函数对象、一个 lambda 表达式或一个指向函数的指针构造一个函数类型的对象,一切顺利(我可以调用该对象并调用相关函数)。
但是,如果我尝试从另一个函数复制构造(或移动构造)一个函数,编译器会将调用仅绑定到采用任意对象的构造函数(具有模板参数 Functor 和通用引用的广义构造函数函数参数),导致崩溃。
我认为如果我这样调用构造函数:
Function<void(double)> fp4(&FreeFunction);
fp4(1.2);
Function<void(double)> fp5 = fp4; // copy construction
应该调用复制构造函数,因为它更专业。
我按照书上的例子做了,但我肯定做错了。
我认为这是本书的缺陷。
the copy constructor should be called, since it's more specialized
template <typename Functor> Function(Functor &&);
是更好的匹配。
typename Functor
推导为Function<...> &
后,构造函数变成Function(Function &);
,传一个non-const比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
可能会被继承,在copy的实现中派生 class 的构造函数,Function
的复制构造函数可以使用派生 class 类型的参数调用。
我正在读 "C++ templates, complete guide" 一书,第 22 章以 std::function-linke class:[=12= 的示例介绍了类型擦除的概念]
#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...);
}
这个 class 只管理为 FunctorWrapper 类型的对象分配的内存,它是一个 class 模板,代表不同种类的函子(或可调用对象)。
如果我从一个函数对象、一个 lambda 表达式或一个指向函数的指针构造一个函数类型的对象,一切顺利(我可以调用该对象并调用相关函数)。
但是,如果我尝试从另一个函数复制构造(或移动构造)一个函数,编译器会将调用仅绑定到采用任意对象的构造函数(具有模板参数 Functor 和通用引用的广义构造函数函数参数),导致崩溃。
我认为如果我这样调用构造函数:
Function<void(double)> fp4(&FreeFunction);
fp4(1.2);
Function<void(double)> fp5 = fp4; // copy construction
应该调用复制构造函数,因为它更专业。 我按照书上的例子做了,但我肯定做错了。
我认为这是本书的缺陷。
the copy constructor should be called, since it's more specialized
template <typename Functor> Function(Functor &&);
是更好的匹配。
typename Functor
推导为Function<...> &
后,构造函数变成Function(Function &);
,传一个non-const比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
可能会被继承,在copy的实现中派生 class 的构造函数,Function
的复制构造函数可以使用派生 class 类型的参数调用。