试试这个下面的代码:
#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?
我很困惑,但我尽量解释。
我看你的拉姆达可以通过两种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()
的比赛,但“空缺版”是首选,因为0
是int
),并从第二个呼叫“INT版”(只有F()
“INT版”匹配) 。
该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
。
要知道为什么是这样的话,可以考虑,如果我们允许function
到function
转换为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::*
但不反之亦然。
调度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)));
}