【问题标题】:No clang warning or error, if C++11 lambda returns wrong type [duplicate]如果C ++ 11 lambda返回错误类型,则没有clang警告或错误[重复]
【发布时间】:2014-07-17 11:33:41
【问题描述】:

如果 lambda 的返回类型不匹配,我的 clang 编译器 (3.3) 似乎不会产生任何错误:

#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 的文档。这里没有错误是设计使然。
  • @SebastianPhilipp 如果你有 Haskell 背景,std::function&lt;b(a)&gt; 不是 a -&gt; b。不要做这样的假设。
  • @R.MartinhoFernandes:为什么不呢?
  • @LightnessRacesinOrbit 返回值的函数可以在不返回值的函数的任何地方使用。它是返回类型协方差。

标签: c++ c++11 types lambda clang


【解决方案1】:

std::function 是支持特定调用签名†的可调用对象的多态容器。

示例中的 lambda 可以像调用 void() 函数一样被调用;忽略返回值在 C++ 中从来都不是类型错误(这是一个好主意还是坏主意是另一个问题)。

因此,std::function&lt;void()&gt; 允许这样的对象。显示的第一个程序完全有效。

然而,第二个程序中的 lambda 不能在 A() 函数可以调用的任何地方调用:

void f(A const&);
f(the_lambda()); // nope!

所以第二个程序无效,编译器正确地报告了这一点。

如果您希望在这种情况下出现类型错误,则需要进行自己的类型检查。在这种情况下,您可以简单地static_assert 使std::result_of&lt;T()&gt;::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

【讨论】:

  • “类型擦除”这个词可能值得一提。
  • “在 C++ 中忽略返回值从来都不是类型错误” INVOKE 需要隐式转换为返回类型。没有类型可以隐式转换为void,因为隐式转换定义为T t=e;,而T 在这里是void。 (另请参阅:is_convertible)在void 返回类型之前存在一个异常LWG 870,但此异常已被删除。另见:stackoverflow.com/a/9343400/420683
【解决方案2】:

您看到的是未定义行为的轻微症状、标准错误或编译器错误。

一般规则是std::function&lt;A(B...)&gt; 并不强制传递的函数对象或指针的签名与A(B...) 完全匹配,而是要求它们兼容。

标准通过调用类型为B... 的传入可调用对象来描述这一点,结果可隐式转换为A

现在,问题是没有任何东西可以隐式转换为void。可以说,即使void 也不会隐式转换为void

一些编译器采用此子句,并将其解释为意味着 std::function&lt;void(B...)&gt; 只能从使用 B... 类型调用时返回 void 的可调用对象构造。其他人将其解释为意味着 std::function&lt;void(B...)&gt; 可以从任何可以使用 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
{};

这是一个特征,它检查使用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,除了我正在拦截通用构造并应用您的测试。

【讨论】:

  • “如果您构造 std::function 的对象违反了标准中规定的要求,我不确定标准规定该语言应该做什么。” @987654321 @ 说它是 UB,我同意,考虑到 [res.on.required]
  • 为什么不对operator() 使用尾随返回类型?
  • @dyp 在 C++1y 中,我只会使用 auto。一些编译器不支持尾随返回类型中的 this 范围变量。因为我能够在没有尾随或this 捕获的情况下做到这一点,所以我做到了?
  • 哦,是的,我记得尾随返回类型中成员变量(和/或this)的一些奇怪问题。
  • @dyp 确实激励我改进返回类型
【解决方案3】:

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

【讨论】:

  • 这就是我所期望的。这是特定于 Mac OS 还是 Clang 3.4 中的功能?
  • @SebastianPhilipp:我将以 800 美元的价格向您出售我的 2011 Macbook Air,并安装了神圣的 Clang。
  • @SebastianPhilipp 说,为什么这是你所期望的?为什么人们会期望一种与设计目标相反的行为?
  • @SebastianPhilipp:C++ 不够原始,无法仅在这种特定情况下处理。预期的行为是 std::function&lt;void()&gt; 在这种情况下简单地丢弃返回值。
  • @SebastianPhilipp 该标准包含奇怪的东西。以std::bind 为例。
【解决方案4】:

std::function&lt;R(Args...)&gt; 的构造函数需要一个参数f,即Callable,参数类型为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)();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-03-22
    • 2014-12-05
    • 2019-09-01
    • 2014-02-24
    • 2017-08-05
    • 1970-01-01
    • 2016-01-24
    • 1970-01-01
    相关资源
    最近更新 更多