【问题标题】:Overloaded lambdas in C++ and differences between clang and gccC++ 中重载的 lambda 以及 clang 和 gcc 之间的区别
【发布时间】:2015-06-22 01:33:18
【问题描述】:

我正在玩一个在 C++ 中重载 lambda 的技巧。具体来说:

// For std::function
#include <functional>

// For std::string
#include <string>

// For std::cout
#include <iostream>

template <class... F>
struct overload : F... {
    overload(F... f) : F(f)... {}
};      

template <class... F>
auto make_overload(F... f) {
    return overload<F...>(f...);
}

int main() {

    std::function <int(int,int)> f = [](int x,int y) {
        return x+y;
    };
    std::function <double(double,double)> g = [](double x,double y) {
        return x+y;
    };
    std::function <std::string(std::string,std::string)> h = [](std::string x,std::string y) {
        return x+y;
    };

    auto fgh = make_overload(f,g,h);
    std::cout << fgh(1,2) << std::endl;
    std::cout << fgh(1.5,2.5) << std::endl;
    std::cout << fgh("bob","larry") << std::endl;
}

现在,上面的程序在 clang 中编译并运行良好:

$ clang++ -g -std=c++14 test01.cpp -o test01
$ ./test01
3
4
boblarry

它不能在 gcc 中编译:

$ g++ -g -std=c++14 test01.cpp -o test01
test01.cpp: In function 'int main()':
test01.cpp:36:25: error: request for member 'operator()' is ambiguous
     std::cout << fgh(1,2) << std::endl;
                         ^
In file included from test01.cpp:5:0:
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional:2434:5: note: candidates are: _Res std::function<_Res(_ArgTypes ...)>::operator()(_ArgTypes ...) const [with _Res = std::basic_string<char>; _ArgTypes = {std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >}]
     function<_Res(_ArgTypes...)>::
     ^
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional:2434:5: note:                 _Res std::function<_Res(_ArgTypes ...)>::operator()(_ArgTypes ...) const [with _Res = double; _ArgTypes = {double, double}]
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional:2434:5: note:                 _Res std::function<_Res(_ArgTypes ...)>::operator()(_ArgTypes ...) const [with _Res = int; _ArgTypes = {int, int}]
test01.cpp:37:29: error: request for member 'operator()' is ambiguous
     std::cout << fgh(1.5,2.5) << std::endl;
                             ^
In file included from test01.cpp:5:0:
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional:2434:5: note: candidates are: _Res std::function<_Res(_ArgTypes ...)>::operator()(_ArgTypes ...) const [with _Res = std::basic_string<char>; _ArgTypes = {std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >}]
     function<_Res(_ArgTypes...)>::
     ^
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional:2434:5: note:                 _Res std::function<_Res(_ArgTypes ...)>::operator()(_ArgTypes ...) const [with _Res = double; _ArgTypes = {double, double}]
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional:2434:5: note:                 _Res std::function<_Res(_ArgTypes ...)>::operator()(_ArgTypes ...) const [with _Res = int; _ArgTypes = {int, int}]
test01.cpp:38:35: error: request for member 'operator()' is ambiguous
     std::cout << fgh("bob","larry") << std::endl;
                                   ^
In file included from test01.cpp:5:0:
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional:2434:5: note: candidates are: _Res std::function<_Res(_ArgTypes ...)>::operator()(_ArgTypes ...) const [with _Res = std::basic_string<char>; _ArgTypes = {std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >}]
     function<_Res(_ArgTypes...)>::
     ^
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional:2434:5: note:                 _Res std::function<_Res(_ArgTypes ...)>::operator()(_ArgTypes ...) const [with _Res = double; _ArgTypes = {double, double}]
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional:2434:5: note:                 _Res std::function<_Res(_ArgTypes ...)>::operator()(_ArgTypes ...) const [with _Res = int; _ArgTypes = {int, int}]
Makefile:2: recipe for target 'all' failed
make: *** [all] Error 1

为什么会有差异?作为记录,我使用的是 gcc 4.9.2 和 clang 3.5.0。


编辑 1

显然,这段 sn-p 代码在 VC 上也无法编译,并且已经是 reported。话虽如此,Sean Middleditch 发布了重载代码的工作版本:

template<class F1, class... Fs>
struct overload : F1, overload<Fs...>
{
    using F1::operator();
    using overload<Fs...>::operator();
    overload(F1 f1, Fs... fs) : F1(f1), overload<Fs...>(fs...) {}
};

template<class F1>
struct overload<F1> : F1
{
    using F1::operator();
    overload(F1 f1) : F1(f1) {}
};


template <class... F>
auto make_overload(F... f) {
    return overload<F...>(f...);
}

我仍然有兴趣了解为什么这个版本的重载 lambda 代码有效,但原来的无效。

【问题讨论】:

  • 这可能只是因为您简化了此问题的代码,但由于您使用的是 C++14,[](auto x, auto y){return x+y;} 将生成具有相同重载功能的 lambda。
  • @DrewDormann 当然。真的,我只是想提出一个例子来说明发生了什么。以后,我想用它来处理更复杂的情况。
  • @dyp 不,这种情况涉及在(fgh).operator() ([over.call.object]/p1) 的上下文中对operator() 的标准查找。该查找的结果显然是模棱两可的。
  • 在完全没有必要时停止使用std::function。这是一种不好的做法,经验不足的人会从这样的代码中学习。

标签: c++ c++11 gcc clang overload-resolution


【解决方案1】:

在我看来像一个 Clang 错误。

一般规则是不同基类中的同名成员函数不重载。例如:

struct Foo { void bar(); };
struct Baz { void bar(int); };
struct Quux : Foo, Baz { };

int main() { Quux().bar(); } // error on both GCC and Clang

无论出于何种原因,Clang 都无法诊断出 operator() 的这种歧义。

using-declaration 将命名的基类成员提升到派生类范围,允许它们重载。因此:

struct Quux_2 : Foo, Baz { using Foo::bar; using Baz::bar; };
Quux_2().bar(); // OK.

在代码的工作版本中,using 声明递归地将模板参数中的每个 operator() 声明带入最派生类的范围,从而允许它们重载。

【讨论】:

  • 这是因为名称查找是在重载解析开始之前完成的,并且名称查找会找到 Foo::barBaz::bar 两者都不是首选?
【解决方案2】:

原代码不应该编译,这里gcc是正确的。见[class.member.lookup]:

否则(即 C 不包含 f 的声明或结果声明集为空),S(f,C) 为 最初是空的。如果 C 有基类,计算 f 在每个直接基类子对象 Bi 中的查找集, 并将每个这样的查找集 S(f,Bi) 依次合并为 S(f,C)。
— [..]
— 否则,如果 S(f,Bi) 和 S(f,C) 的声明集不同,则合并不明确……

初始声明集是空的(overload 没有方法) - 所以合并所有基,它们都有不同的集。所以合并应该失败。该规则仅适用于 overload 的声明集为空的情况,这就是显式添加 using F1::operator() 有效的原因。

【讨论】:

    猜你喜欢
    • 2016-08-01
    • 2015-12-07
    • 2013-12-30
    • 2015-08-13
    • 1970-01-01
    • 2016-04-07
    • 2014-04-11
    相关资源
    最近更新 更多