尝试编译时,令人惊讶的是,它会产生错误,因为lambda函数的auto
参数已解析为std::string
,并且编译器不知道如何转换{{1调用std::string
时,调用int
或Widget
。
但是,我想知道为什么编译器选择第二个test
函数而不是第一个函数,当第一个函数成功时:
invok
该示例已从a C++ proposal复制。
答案 0 :(得分:3)
编译器没有选择#2。它试图决定是否可以选择#2。
为此,它询问“这个通用lambda可以转换为std::function<bool(std::string)>
”吗?
std::function
的转换构造函数说“只有它可以使用std::string
rvalue调用,并且结果类型可以转换为bool
”。
编译器尝试将auto
推导为std::string
,替换为函数调用运算符的签名...成功!糟糕,返回类型为auto
,它需要一个实际类型来回答“可转换”问题。因此它实例化函数调用操作符模板的主体以找出返回类型。
哎哟。毕竟,身体对std::string
无效。接下来是硬错误和爆炸。
答案 1 :(得分:3)
你的重载仍然不明确(某种程度上),但是在尝试两种可能性(传递int或字符串)时确定代码遇到硬错误的方法。
要查看歧义,请在lambda中添加->bool
,这样就不必编译正文以确定返回值。
lambda的主体不在一个替换失败导致错误的区域。相反,你会遇到一个很难的错误。
简单的解决方法是让你的lambda明确地使用int。
如果您想要通用解决方案:
#define RETURNS(...) \
noexcept(noexcept(__VA_ARGS__)) \
-> decltype(__VA_ARGS__) \
{ return __VA_ARGS__; }
#define OVERLOADS_OF(...) \
[](auto&&...args) \
RETURNS( __VA_ARGS__( decltype(args)(args)... ) )
然后
invok(OVERLOADS_OF(test));
做(至少更接近)正确的事情。
此宏将失败从lambda的主体移动到尾随返回类型。并且失败(该字符串不能传递给测试)现在发生在替换失败不会导致错误(SFINAE)的上下文中。所以一切正常。
SFINAE友好的准确规则以及不需要阅读标准的规则。但是,经验法则是,编译器不必将函数体中的错误视为替换错误,并且访问未定义类的内容是一个难以理解的错误。第一个因为它似乎是一个合理的位置来绘制一条线并使编译器编写者更容易;第二个因为替代品是精神错乱或ODR错误诱饵。
在实践中,标准SFINAE规则更为神秘,最后我在C ++ 14中检查过,在模板类部分特化期间需要省略SFINAE:每个编译器都支持它。但也许我误解了它。无论如何,我使用的经验法则似乎与标准文本一样有用。两者都不会是完美的。