C++ 带默认参数的函数上的SFINAE-自由函数vs运算符()
我一直在研究它如何处理带有默认参数的函数。令我惊讶的是,对于自由函数和C++ 带默认参数的函数上的SFINAE-自由函数vs运算符(),c++,c++11,language-lawyer,sfinae,C++,C++11,Language Lawyer,Sfinae,我一直在研究它如何处理带有默认参数的函数。令我惊讶的是,对于自由函数和操作符(),结果是不同的: 模板 自动功能(F)->取消类型(F(42)) { INTA=51; 返回f(51); } 模板 自动功能(F)->取消类型(F(42,42)) { int a=0; int b=10; 返回f(a,b); } int defaultFree(int a,int b=0) { 返回a; } 自动默认lambda=[](int a,int b=0) { 返回a; }; int foo() { 返回函数
操作符()
,结果是不同的:
模板
自动功能(F)->取消类型(F(42))
{
INTA=51;
返回f(51);
}
模板
自动功能(F)->取消类型(F(42,42))
{
int a=0;
int b=10;
返回f(a,b);
}
int defaultFree(int a,int b=0)
{
返回a;
}
自动默认lambda=[](int a,int b=0)
{
返回a;
};
int foo()
{
返回函数(无默认值);
//返回func(defaultLambda);
}
上面的func(defaultFree)
版本可以编译,而func
两个模板都可用。正如预期的那样,它选择第二个参数,因为默认参数不被视为函数签名的一部分。实际上,删除第二个func
模板会导致编译错误
但是,func(defaultLambda)
由于模棱两可而无法执行:两个func
模板都匹配。删除任何一个都会使此版本编译
(如果您使用类似的操作符()手动编写结构
,当然也会发生同样的情况。最新的gcc
、clang
和MSVC
也都同意。)
对于操作符()
,默认参数被认为是在未赋值的SFINAE上下文中,而不是自由函数中的原因是什么?当您将自由函数作为参数传递时,它会经历函数到指针的转换。当这种情况发生时,默认参数()就消失了。现在它是一个指针,指向一个包含两个参数的函数,因此只有一个SFINAE检查可以传递给它
lambda的类型没有经过这样的调整。未计算的表达式必须包含运算符()
的重载解析,该重载解析将查找带有默认参数的声明,从而允许在带有单个参数的调用中使用该声明
当无约束的lambda被迫转换为函数指针(例如,代码> FUNC(+ Debug Tlambda);,@ YSC的礼貌)时,歧义消失,原因相同。
< P>函数名称不是C++对象的名称。< /P>
相反,当您使用函数名时,会发生一系列转换。重载解析基于调用或(隐式或显式)转换上下文完成,并生成指针
函数的默认参数是重载解析的一部分。它们永远不会作为函数指针类型的一部分传递
您可以创建一个简单的包装器,将函数名转换为函数对象:
#define RETURNS(...) \
noexcept(noexcept(__VA_ARGS__)) \
-> decltype(__VA_ARGS__) \
{ return __VA_ARGS__; }
#define OVERLOADS_OF(...) \
[](auto&&...args) \
RETURNS( __VA_ARGS__( decltype(args)(args)... ) )
使用此选项,您可以修改代码:
return func(OVERLOADS_OF(defaultFree));
并获取func
和SFINAE要考虑的默认参数,以导致歧义
现在,OVERLOADS\u OF(defaultFree)
是一个函数对象,SFINAE测试它的参数是否可以传递给一个名为defaultFree
的可调用对象。这允许传递1个参数
函数对象不是函数。lambda是一个函数对象,它是
的重载的返回类型。可以传入函数对象并考虑其重载的操作符();他们可以记住自己的默认参数、do SFINAE等
所以当你通过lambda时,这两种可能性都是合法的。当您传递一个函数时,它将成为指向该函数的无默认参数调用的函数指针,并且该函数明确不接受1个参数
要解决你的问题,你应该让一个过载看起来比另一个好
一种方法是使用…
:
namespace impl {
template <typename F>
auto func(F f,...) -> decltype(f(42))
{
int a = 51;
return f(51);
}
template <typename F>
auto func(F f, int) -> decltype(f(42, 42))
{
int a = 0;
int b = 10;
return f(a, b);
}
}
template <typename F>
auto func(F f) -> decltype( impl::func(f, 0) )
{
return impl::func(f, 0);
}
namespace impl{
模板
自动功能(F,…)->decltype(F(42))
{
INTA=51;
返回f(51);
}
模板
自动函数(F,int)->decltype(F(42,42))
{
int a=0;
int b=10;
返回f(a,b);
}
}
模板
autofunc(F)->decltype(impl::func(F,0))
{
返回impl::func(f,0);
}
诀窍是当你通过0
时,int
优先于…
您还可以更加明确,生成诸如“可以使用1个参数调用”、“可以使用2个参数调用”之类的特征,然后声明仅当可以使用1个参数而不是2个参数调用时才启用1 arg大小写
还有基于标签调度的过载解析排序技术。+1。我总是假设(例如)void f(inta=0){…}
本质上与void f(inta){…}
加上void f(){f(0);}
,但哇,显然不是!
namespace impl {
template <typename F>
auto func(F f,...) -> decltype(f(42))
{
int a = 51;
return f(51);
}
template <typename F>
auto func(F f, int) -> decltype(f(42, 42))
{
int a = 0;
int b = 10;
return f(a, b);
}
}
template <typename F>
auto func(F f) -> decltype( impl::func(f, 0) )
{
return impl::func(f, 0);
}