如何使用std :: shared_ptr <void>和另一种类型的std :: shared_ptr进行函数重载?

时间:2019-02-05 21:07:59

标签: c++ c++11 std shared-ptr overloading

尝试以下代码:

#include <functional>
#include <memory>

class C {
    public:
    void F(std::function<void(std::shared_ptr<void>)>){}
    void F(std::function<void(std::shared_ptr<int>)>){}
};

int main(){
    C c;
    c.F([](std::shared_ptr<void>) {});
}

您会看到一个编译错误:

prog.cc:12:7: error: call to member function 'F' is ambiguous
    c.F([](std::shared_ptr<void>) {});
    ~~^
prog.cc:6:10: note: candidate function
    void F(std::function<void(std::shared_ptr<void>)>){}
         ^
prog.cc:7:10: note: candidate function
    void F(std::function<void(std::shared_ptr<int>)>){}
         ^

有什么方法可以解决这种歧义吗?也许与SFINAE合作?

2 个答案:

答案 0 :(得分:6)

我很困惑,但是我尝试做一个解释。

我发现std::function<void(std::shared_ptr<void>)>std::function<void(std::shared_ptr<int>)>都可以接受您的lambda;您可以验证以下两行都可以编译

std::function<void(std::shared_ptr<void>)>  f0 = [](std::shared_ptr<void>){};
std::function<void(std::shared_ptr<int>)>   f1 = [](std::shared_ptr<void>){};

这是因为(我想)指向int的共享指针可以转换为指向void的共享指针;您可以验证以下行是否编译

std::shared_ptr<void> sv = std::shared_ptr<int>{};

在这一点上,我们可以看到呼叫

c.F([](std::shared_ptr<void>) {});

您没有将std::function<void(std::shared_ptr<void>)>传递给F();您传递的对象可以同时转换为std::function<void(std::shared_ptr<void>)>std::function<void(std::shared_ptr<int>)>;因此可以用来调用F()的两个版本的对象。

所以模棱两可。

  

有什么方法可以解决这种歧义吗?也许与SFINAE合作?

也许带有标签分派。

您可以添加未使用的参数和模板F()

void F (std::function<void(std::shared_ptr<void>)>, int)
 { std::cout << "void version" << std::endl; }

void F (std::function<void(std::shared_ptr<int>)>, long)
 { std::cout << "int version" << std::endl; }

template <typename T>
void F (T && t)
 { F(std::forward<T>(t), 0); }

这样打电话

c.F([](std::shared_ptr<void>) {});
c.F([](std::shared_ptr<int>){});

您从第一次调用中获得“无效版本”(两个非模板F()都匹配,但首选“无效版本”,因为0int)和“ int版本” ”(只有F()“ int版本”匹配)。

答案 1 :(得分:4)

为什么会发生

answer by max66基本上解释了正在发生的事情。但是,可能有些令人惊讶:

  • 您可以将std::shared_ptr<int>隐式转换为std::shared_ptr<void>,而不能反向转换。

  • 您可以将std::function<void(std::shared_ptr<void>)>隐式转换为std::function<void(std::shared_ptr<int>)>,而不能反向转换。

  • 您可以将参数类型为std::shared_ptr<void>的lambda隐式转换为std::function<void(std::shared_ptr<int>)>

  • 您不能将参数类型为std::shared_ptr<int>的lambda隐式转换为std::function<void(std::shared_ptr<void>)>

原因是,在比较函数接口是更通用还是更特定时,规则是返回类型必须为“协变量”,而参数类型必须为“协变量”(Wikipedia;另请参见{{ 3}})。也就是说,

  

给出(伪代码)函数接口类型

C func1(A1, A2, ..., An)
D func2(B1, B2, ..., Bn)
     

然后,如果func2可以转换为func1且每个{{1}都可以转换为D类型的实例的任何函数,也都是C类型的实例的}可以转换为相应的Ai

要了解为什么会这样,请考虑如果我们允许Bi类型的functionfunction转换然后尝试调用它们会发生什么情况。

如果我们将std::function<std::shared_ptr<T>>转换为std::function<void(std::shared_ptr<void>)> a;,那么std::function<void(std::shared_ptr<int>)> b;的行为就像一个包装,其中包含b的副本并将呼叫转发给它。然后a可以与任何b一起调用。可以将其传递给std::shared_ptr<int> pi;的副本吗?可以,因为它可以将a转换为std::shared_ptr<int>

如果我们将std::shared_ptr<void>转换为std::function<void(std::shared_ptr<int>)> c;,那么std::function<void(std::shared_ptr<void>)> d;的行为就像一个包装,其中包含d的副本并将呼叫转发给它。然后c可以与任何d一起调用。可以将其传递给std::shared_ptr<void> pv;的副本吗?不安全!从cstd::shared_ptr<void>没有转换,即使我们想象std::shared_ptr<int>以某种方式尝试使用d或类似的东西,std::static_pointer_cast可能也不会指向{完全{1}}。

从C ++ 17(this SO Q&A)开始,实际的标准规则就是pv构造函数模板的规则

int
     

备注:除非std::function<R(ArgTypes...)>对于参数类型template<class F> function(F f); 的Lvalue可调用并返回类型f,否则此构造函数模板不得参与重载解析。

其中“左值可调用”实质上意味着具有给定类型的完美转发参数的函数调用表达式是有效的,并且如果ArgTypes...不是 cv R ,则表达式可以隐式转换为R,另外还需考虑void是指向成员的指针和/或某些参数类型为R的情况。

在尝试从任何可调用类型转换为f时,此定义实质上会自动检查互变量参数类型,因为它会检查目标std::reference_wrapper<X>类型的参数类型是否是源的有效参数可调用类型(允许允许的隐式转换)。

(在C ++ 17之前,std::function模板构造函数根本没有任何SFINAE样式的限制。对于像这样的重载情况以及试图检查转换是否有效的模板,这是个坏消息。 )

请注意,至少在C ++语言中,至少在另一种情况下会出现参数类型的矛盾(即使这不是允许的虚函数替代)。可以将指向成员值的指针视为一个函数,该函数将类对象作为输入,并返回成员左值作为输出。 (并且从指向成员的指针初始化或分配function会完全以这种方式解释含义。)并且鉴于类std::function::function(F)是类std::function的公共明确基础,我们拥有B可以隐式转换为D但不能反之,而D*可以转换为B*但不能反之。

做什么

max66建议的标签分配是一种解决方案。

或者以SFINAE的方式,

MemberType B::*