如何做好函数的std :: shared_ptr的超载 而另一种类型的std ::的shared_ptr的?

问题描述 投票:13回答:2

试试这个下面的代码:

#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?

c++ c++11 std shared-ptr overloading
2个回答
6
投票

我很困惑,但我尽量解释。

我看你的拉姆达可以通过两种std::function<void(std::shared_ptr<void>)>std::function<void(std::shared_ptr<int>)>被接受;您可以验证这两个以下行编译

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版”匹配) 。


4
投票

Why it happens

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;另见this SO Q&A)。那是,

鉴于(伪代码)功能的接口类型

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

然后这是func2类型的实例的任何功能也是func1类型的一个实例,如果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;调用。它可以将它传递给c的副本?不放心!有没有从std::shared_ptr<void>转换std::shared_ptr<int>,即使我们想象d莫名其妙地尝试使用std::static_pointer_cast或相似,pv可能不会在int指向所有。

实际的标准规则,因为C ++ 17([func.wrap.func.con]/7)是用于std::function<R(ArgTypes...)>构造模板

template<class F> function(F f);

备注:此构造模板不得参与重载解析,除非f是左值调用的参数类型ArgTypes...和返回类型R

其中,“左值调用”主要是指与给定类型的完美转发参数的函数调用表达式是有效的,如果R不CV void,表达式可以隐式转换为R,加上案件时考虑f是一个指针到构件和/或一些参数类型是std::reference_wrapper<X>

这个定义尝试从任何类型的可调用一个转换到std::function时基本上自动检查逆变参数类型,因为它会检查参数类型的目的地function类型是否有效的参数给源可调用类型(允许允许隐式转换)。

(C ++ 17之前,std::function::function(F)模板的构造并没有带任何SFINAE风格的限制,这是超载这样的情况和模板坏消息,试图检查转换是否有效。)

注意参数类型的逆变在C ++语言的至少一个其他情况却显示出来(即使它不是一个允许的虚函数覆盖)。一个指针,指向部件的值可以被认为是这需要一个类对象作为输入,并返回构件左值作为输出的函数。 (和初始化或从指针到成员分配std::function会正是这样解释的意思。)而鉴于该类B是类D的公开明确的基础上,我们有一个D*可以隐式转换为B*但反过来-versa和MemberType B::*可以转换为MemberType D::*但不反之亦然。

What to do

调度max66表明该标签是一个解决方案。

或为SFINAE方式,

void F(std::function<void(std::shared_ptr<void>)>);
void F(std::function<void(std::shared_ptr<int>)>);

// For a type that converts to function<void(shared_ptr<void>)>,
// call that overload, even though it likely also converts to
// function<void(shared_ptr<int>)>:
template <typename T>
std::enable_if_t<
    std::is_convertible_v<T&&, std::function<void(std::shared_ptr<void>)>> &&
    !std::is_same_v<std::decay_t<T>, std::function<void(std::shared_ptr<void>)>>>
F(T&& func)
{
    F(std::function<void(std::shared_ptr<void>)>(std::forward<T>(func)));
}
© www.soinside.com 2019 - 2024. All rights reserved.