6502
6502

Reputation: 114461

C++11 std::function const overload ambiguity

I'm having a problem with a part of a larger program where something that I'd say is not ambiguous is considered ambiguous by both g++ and clang++

#include <functional>
#include <string>

struct Foo {
    Foo(int) {}
    Foo(std::string) {}
    operator int () const { return 42; }
    operator std::string () const { return ""; }

    void foo(std::function<void(Foo&, int)>f);
    void foo(std::function<void(const Foo&, int)>f) const; // xxx

    void foo(std::function<void(const std::string&, Foo&)>f);
    void foo(std::function<void(const std::string&, const Foo&)>f) const;

    void bar() const {
        this->foo([](const Foo&, int){}); // xxx
    }
};

I'd expect the invocation of ::foo made in bar to be unambiguously resolved to the const version marked with xxx, while instead both compilers complain that the overload resolution is ambiguous:

g++ -std=c++11 -c -Wall amb.cpp 
amb.cpp: In member function ‘void Foo::bar() const’:
amb.cpp:18:40: error: call of overloaded ‘foo(Foo::bar() const::<lambda(const Foo&, int)>)’ is ambiguous
         this->foo([](const Foo&, int){});
                                        ^
amb.cpp:12:10: note: candidate: void Foo::foo(std::function<void(const Foo&, int)>) const
     void foo(std::function<void(const Foo&, int)>f) const;
          ^
amb.cpp:15:10: note: candidate: void Foo::foo(std::function<void(const std::basic_string<char>&, const Foo&)>) const
     void foo(std::function<void(const std::string&, const Foo&)>f) const;
          ^

Why it's not clear which version I want to call? How can I work around this problem?

Upvotes: 2

Views: 1747

Answers (2)

6502
6502

Reputation: 114461

The problem is that some type information is lost when passing from a lambda to an std::function object (in particular the argument types).

To be more specific for example a function accepting std::function<void(int)> and another accepting std::function<void(double)> are considered equally good overloads for a void lambda accepting an int argument.

For example with

#include <functional>

void foo(std::function<void(int)>f);
void foo(std::function<void(double)>f);

void bar() {
    foo([](int){});
}

the call in bar is considered ambiguous

amb2.cpp: In function ‘void bar()’:
amb2.cpp:8:18: error: call of overloaded ‘foo(bar()::<lambda(int)>)’ is ambiguous
     foo([](int){});
                  ^
amb2.cpp:4:6: note: candidate: void foo(std::function<void(int)>)
 void foo(std::function<void(int)>f);
      ^
amb2.cpp:5:6: note: candidate: void foo(std::function<void(double)>)
 void foo(std::function<void(double)>f);
      ^

Workaround

A solution is to explicitly create the proper std::function object with:

this->foo(std::function<void(const Foo&, int)>([](const Foo&, int){}));

Upvotes: 2

Some programmer dude
Some programmer dude

Reputation: 409146

You could make the constructor taking the std::string argument explicit. That way the compiler can't make an implicit conversion from std::string to Foo.

Upvotes: 0

Related Questions