【问题标题】:templates and overload resolution模板和重载决议
【发布时间】:2019-07-21 15:34:06
【问题描述】:

我正在阅读“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 类型的对象分配的内存,它是一个类模板,代表不同类型的仿函数(或可调用对象)。

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

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

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

Function<void(double)> fp4(&FreeFunction);
fp4(1.2);

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

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

【问题讨论】:

  • 为什么在这种情况下应该调用复制构造函数 fp5 = fp4 ?复制 ctor 在其签名中有const。但是fp4 是非常量对象。因此,完美转发的重载比F(const F&amp;) 具有更好的匹配性,并且在这种情况下使用它。当您将 fp4 更改为 const 对象时,将调用复制 ctor。因为当编译器有两个具有相同匹配的重载时——一个是普通函数,另一个是模板版本,普通函数是首选。

标签: c++ templates copy-constructor


【解决方案1】:

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

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

template &lt;typename Functor&gt; Function(Functor &amp;&amp;); 是更好的匹配。

typename Functor 推导出为Function&lt;...&gt; &amp; 后,构造函数变成Function(Function &amp;);,如果你传递一个非常量对象给它,它比Function(const Function &amp;); 匹配更好。

您可以使用 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 &amp;&amp;),然后是移动分配)。

【讨论】:

  • 其实这是我实现的一个缺陷。本书明确定义了一个 Function(Function &other),它在将参数转换为 const Function & 后委托给 Function(Function const &other)。现在它可以正常工作了
【解决方案2】:

是的,不幸的是,转发引用版本比复制构造函数更匹配,复制构造函数需要隐式转换为对 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的拷贝构造函数可能使用派生类类型的参数调用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-04-20
    • 1970-01-01
    • 1970-01-01
    • 2022-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-25
    相关资源
    最近更新 更多