看起来我的clang编译器(3.3)不会产生任何错误,如果lambda的返回类型不匹配:
#include <functional>
typedef std::function<void()> voidFunc;
void foo(voidFunc func)
{
func();
}
int main()
{
int i = 42;
foo([i]()
{
return i;
});
return 0;
}
编译这段代码没有显示任何错误:
clang++ -c -Xclang -stdlib=libc++ -std=c++11 -Weverything -Wno-c++98-compat -Wno-missing-prototypes -o foo.o foo.cpp
对于这样的问题,我如何生成类型错误?
编辑:生成一个类型错误:
#include <functional>
struct A {};
struct B {};
typedef std::function<A()> aFunc;
void foo(aFunc func)
{
func();
}
int main()
{
int i = 42;
foo([i]()
{
return B();
});
return 0;
}
错误:
foo2.cpp:16:2: error: no matching function for call to 'foo'
foo([i]() {
^~~
foo2.cpp:8:6: note: candidate function not viable: no known conversion from '<lambda at foo2.cpp:16:6>' to 'aFunc' (aka 'function<A ()>') for 1st argument
void foo(aFunc func)
^
1 error generated.
std::function
是支持特定调用签名的可调用对象的多态容器。
可以像调用void()
函数一样调用示例中的lambda;忽略返回值在c++中从来不是类型错误(这是好主意还是坏主意是另一个问题)。
因此,std::function<void()>
允许这样一个对象。显示的第一个程序是完全有效的。
然而,第二个程序中的lambda不能在A()
函数可以调用的任何地方调用:
void f(A const&);
f(the_lambda()); // nope!
所以第二个程序是无效的,编译器正确地报告它。
如果您希望在这种情况下出现类型错误,则需要自己进行类型检查。在这种情况下,您可以简单地设置static_assert
,即std::result_of<T()>::type
与void
相同。然而,一般来说,这是不可能的,因为在c++中,所有可调用对象(除了退化的void()
)都有不止一个可能的调用签名,这要归功于隐式转换等特性。
dagger;我可能需要解释一下我所说的"呼叫签名"是什么意思。我指的是在实际调用中使用的类型,或者可能在调用+返回值赋值中使用的类型,而不是在声明的签名中显式出现的类型。考虑下面的代码:
long f(double);
double d;
int i;
long a = f(d); // call signature is long(double)
// called with double, returning into a long
short b = f(i); // call signature is short(int)
// called with int, returning into a short
您所看到的是未定义行为的轻微症状,或者是标准中的错误,或者是编译器中的错误。
一般规则是std::function<A(B...)>
不强制传递的函数对象或指针的签名与A(B...)
完全匹配,而是要求它们兼容。
标准通过调用类型为B...
的传入可调用对象来描述这一点,并且结果可以隐式转换为A
。
现在,问题是没有什么可以隐式转换为void
。可以说,即使void
也不会隐式地转换为void
。
一些编译器接受此子句,并将其解释为std::function<void(B...)>
只能由使用B...
类型调用时返回void
的可调用对象构造。其他人将其解释为std::function<void(B...)>
可以由任何可以用B...
类型调用的可调用对象构造。
我不确定如果您构造std::function
的对象违反了标准中规定的要求,该语言应该做什么。
现在,为了解决这个问题,你可以这样写:
template<typename T, typename Sig>
struct invoke_return_value_matches;
template<typename T, typename R, typename... Args>
struct invoke_return_value_matches<T, R(Args...)>:
std::is_same< typename std::result_of<T(Args...)>::type, R >::type
{};
这是一个trait,检查用Args...
调用T
的返回值是否与R
完全匹配。
如果我们想为std::function
创建一个快速包装器,强制对返回值进行精确匹配:
template<typename Sig>
struct exact_function {
std::function<Sig> f;
template<typename...Args>
typename std::result_of< (Sig*)(Args...) >::type
operator()(Args&&...args) const {
return f(std::forward<Args>(args)...);
}
operator std::function<Sig>() const { return f; }
operator std::function<Sig>() && { return std::move(f); }
exact_function() = default;
exact_function(exact_function const&)=default;
exact_function(exact_function&&)=default;
exact_function& operator=(exact_function const&)=default;
exact_function& operator=(exact_function&&)=default;
template<
typename T,
typename=typename std::enable_if<invoke_return_type_matches<T,Sig>::value>::type
>
exact_function( T&& t ):f(std::forward<T>(t)) {}
exact_function( Sig* raw_func ):f(raw_func) {}
};
应该非常接近您想要的内容。我主要只是转发垃圾到std::function
内部,除了我拦截通用结构和应用你的测试。
Mac OS的Clang 3.4拒绝你的代码:
error: no matching function for call to 'foo'
foo([i]() {
^~~
note: candidate function not viable: no known conversion
from '<lambda at t.cpp:10:9>' to 'voidFunc'
(aka 'function<void ()>') for 1st argument
void foo(voidFunc func) {
^
clang++ --version
在我的系统上显示:
Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn)
Target: x86_64-apple-darwin13.3.0
std::function<R(Args...)>
的构造函数要求参数f
为Callable
,参数类型为Args...
,返回类型为R
;即 INVOKE
(f, t1, t2, ..., tN)
隐式转换为R
是有效的。
注意,函数参数允许转换,因此没有理由在返回类型上也不允许转换。
如果你想只接受具有特定返回类型的参数,你可以使用result_of
和is_same
:
template<typename F>
auto foo(F&& func) -> typename std::enable_if<std::is_same<
typename std::result_of<F()>::type, void>::value>::type
{
std::forward<F>(func)();
}