Подтвердить что ты не робот

Перегруженные lambdas в С++ и различия между clang и gcc

Я играю с трюком, чтобы перегрузить lambdas на С++. В частности:

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

Очевидно, этот фрагмент кода не удалось скомпилировать на VC и уже был сообщен. При этом Шон Миддлдич опубликовал рабочую версию перегруженного кода:

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

Мне все еще интересно понять, почему эта версия перегруженного лямбда-кода работает, а оригинальная - нет.

4b9b3361

Ответ 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().

A using-declaration поднимает именованные базовые классы в область производного класса, что позволяет им перегружать. Следовательно:

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

В рабочей версии кода декларации using рекурсивно привносят каждое объявление operator() в аргументы шаблона в область самого производного класса, что позволяет им перегружать.

Ответ 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() работает.