没有clang警告或错误,如果c++ 11 lambda返回错误的类型

No clang warning or error, if C++11 lambda returns wrong type

本文关键字:错误 lambda 返回 类型 c++ 如果 clang 警告 没有      更新时间:2023-10-16

看起来我的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()>::typevoid相同。然而,一般来说,这是不可能的,因为在c++中,所有可调用对象(除了退化的void())都有不止一个可能的调用签名,这要归功于隐式转换等特性。


匕首;我可能需要解释一下我所说的"呼叫签名"是什么意思。我指的是在实际调用中使用的类型,或者可能在调用+返回值赋值中使用的类型,而不是在声明的签名中显式出现的类型。考虑下面的代码:

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...)>的构造函数要求参数fCallable,参数类型为Args...,返回类型为R;即 INVOKE (f, t1, t2, ..., tN)隐式转换为R是有效的。

注意,函数参数允许转换,因此没有理由在返回类型上也不允许转换。

如果你想只接受具有特定返回类型的参数,你可以使用result_ofis_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)();
}