Overloaded lambdas in C ++ and differences between clang and gcc

I play with a trick to overload lambdas in C ++. In particular:

// 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; } 

Now this program compiles and works fine in clang:

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

It does not compile in 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 

Why is there a difference? For recording, I use gcc 4.9.2 and clang 3.5.0.




Change 1

Obviously, this piece of code could not be compiled on VC and has already been reported . At the same time, Sean Middleditch published a working version of the overloaded code:

 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...); } 

I'm still curious to understand why this version of the overloaded lambda code works, but the original one doesn't.

+25
c ++ gcc c ++ 11 clang overload-resolution
Apr 15 '15 at 21:27
source share
2 answers

Looks like a Clang bug for me.

The general rule is that member functions of the same name in different base classes are not overloaded. For example:

 struct Foo { void bar(); }; struct Baz { void bar(int); }; struct Quux : Foo, Baz { }; int main() { Quux().bar(); } // error on both GCC and Clang 

For some reason, Clang cannot diagnose this ambiguity for operator() .

A using-declaration takes named base classes to the scope of the derived class, which allows them to overload. Hence:

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

In the working version of the code, using declarations recursively introduce each operator() declaration in the template arguments to the scope of the derived class itself, which allows them to overload.

+14
Apr 15 '15 at 22:00
source share

The source code should not compile, gcc is correct here. See [Class.member.lookup]:

Otherwise (i.e. C does not contain the declaration f or the resulting set of declarations is empty), S (f, C) is initially empty. If C has base classes, compute the search set for f in each subobject of the direct base class Bi, and combine each such search set S (f, Bi), in turn, into S (f, C).
- [..]
- Otherwise, if the sets of declarations S (f, Bi) and S (f, C) are different, the merger is ambiguous ...

The initial set of declarations is empty ( overload has no methods) - so combine all the databases, all of which have different sets. Therefore, the merger should fail. This rule only applies if the overload declaration set is empty, so explicitly adding using F1::operator() works.

+9
Apr 15 '15 at 21:59
source share



All Articles