installed - Lambdas sobrecargadas en C++ y diferencias entre clang y gcc
clang windows (2)
A mí me parece un error de Clang.
La regla general es que las funciones miembro del mismo nombre en diferentes clases base no se sobrecargan. Por ejemplo:
struct Foo { void bar(); };
struct Baz { void bar(int); };
struct Quux : Foo, Baz { };
int main() { Quux().bar(); } // error on both GCC and Clang
Por alguna razón, Clang no puede diagnosticar esta ambigüedad para el
operator()
.
Una
using-declaration
eleva a los miembros de la clase base nombrada al alcance de la clase derivada, lo que les permite sobrecargarse.
Por lo tanto:
struct Quux_2 : Foo, Baz { using Foo::bar; using Baz::bar; };
Quux_2().bar(); // OK.
En la versión funcional del código, las declaraciones de
using
recursivamente llevan cada declaración de
operator()
en los argumentos de la plantilla al alcance de la clase más derivada, lo que les permite sobrecargarse.
Estoy jugando con un truco para sobrecargar lambdas en C ++. Específicamente:
// 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;
}
Ahora, el programa anterior compila y funciona bien en clang:
$ clang++ -g -std=c++14 test01.cpp -o test01
$ ./test01
3
4
boblarry
No se compila en 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
¿Por qué hay una diferencia? Para el registro, estoy usando gcc 4.9.2 y clang 3.5.0.
Editar 1
Evidentemente, este fragmento de código no pudo compilarse también en VC y ya se había reported . Dicho esto, Sean Middleditch publicó una versión funcional del código sobrecargado:
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...);
}
Todavía estoy interesado en entender por qué funciona esta versión del código lambda sobrecargado, pero el original no.
El código original no debe compilarse, gcc es correcto aquí. Ver [class.member.lookup]:
De lo contrario (es decir, C no contiene una declaración de f o el conjunto de declaraciones resultante está vacío), S (f, C) está inicialmente vacío. Si C tiene clases base, calcule el conjunto de búsqueda para f en cada subobjeto de clase base directa Bi, y combine cada conjunto de búsqueda S (f, Bi) a su vez en S (f, C).
- [..]
- De lo contrario, si los conjuntos de declaración de S (f, Bi) y S (f, C) difieren, la fusión es ambigua ...
El conjunto de declaración inicial está vacío (la
overload
no tiene métodos), por lo tanto, combine todas las bases, todas las cuales tienen conjuntos diferentes.
Entonces la fusión
debería
fallar.
Sin embargo, esa regla solo se aplica si el conjunto de declaración de
overload
está vacío, por lo que funciona la adición explícita del
using F1::operator()
.