Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variadic template using lambdas : error with g++ but running with clang++

While playing with variadic templates, classes, functions and lambdas, (from here) I found that following code is running with clang++ while not running with g++ :

#include <iostream>
#include <string>
using namespace std;
template <class... F>
struct overload_set : F... 
{
  overload_set(F... f) : F(f)... {}  
};
template <class... F>
auto overload(F... f) 
{
  return overload_set<F...>(f...);   
}
int main()
{
    auto func = overload (
        [](int &val) { val *= 2; }, 
        [](string &arg) { arg += arg; }, 
        [](char &c) { c = 'x'; }
        );
    int val = 10;
    string str = "stackoverflow";
    char ch = 's';
    cout << val << " : " << str << " : " << ch << endl;
    func(val);
    func(str);
    func(ch);
    cout << val << " : " << str << " : " << ch << endl;
    return 0;
}

For clang : coliru

For g++ : coliru

g++ is giving ambiguous operator() for func(val), func(str) and func(c). I think the operator() must not be ambiguous, as each one is having different arguments.

What's the problem with g++?

like image 878
0x6773 Avatar asked Oct 28 '15 06:10

0x6773


1 Answers

This has little to do with lambdas, variadic templates, operators or any advanced C++1{xy} stuff. Let's simplify:

struct foo
{
    void func(int&){}
};
struct bar
{
    void func(char&){}
};

struct test : foo, bar {};

int main()
{
    test t;
    int i = 1;
    char c = 'a';
    t.func(i);
    t.func(c);
}

This fails to compile in either g++ or clang++. Which is a good thing too, because that's how the language is specified to work.

If we change func to operator(), g++ continues to reject the program but clang++ either accepts or rejects it, depending on how the operator is called:

 t.operator()(c); // rejected
 t(c);            // accepted

Which looks like a clang bug to me.

In order to make the code above compile, a very small change is needed:

struct test : foo, bar {
  using foo::func;
  using bar::func;
};

Now I have no idea how to make pack expansion work in the using directive, or if it's indeed possible. But there's a workaround:

template <class... F> struct overload_set;

template <> struct overload_set<> {};

template <class F> struct overload_set<F> : F {
  using F::operator();
  overload_set(F f) : F(f) {}
};

template <class F, class... Fs>
struct overload_set<F, Fs...> : F, overload_set<Fs...>
{
  overload_set(F f, Fs... fs) : F(f), overload_set<Fs...>(fs...) {}
  using F::operator();
  using overload_set<Fs...>::operator();
};

With this change your code compiles with both g++ and clang++.

like image 175
n. 1.8e9-where's-my-share m. Avatar answered Nov 15 '22 13:11

n. 1.8e9-where's-my-share m.