Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to resolve ambiguous overloaded function call?

When I compile this program with either gcc-4.6.3 or gcc-4.7.2 the compiler gives me an error about the overloaded call being ambiguous:

#include <iostream>
#include <functional>

class Scott
{
    public:
        void func(const bool b = true)
        {
            std::cout << "Called func() with a boolean arg" << std::endl;
        }

        void func(std::function<void(void)> f)
#ifdef WITH_CONST
            const
#endif
        {
            std::cout << "Called func() with a std::function arg" << std::endl;
        }
};


int main (int argc, char *argv[])
{
    Scott s;
    s.func([] (void) { });
}

However, if I make the overloaded function const, it compiles fine & calls the method I did not expect!

devaus120>> g++ -Wall -std=c++11 -DWITH_CONST wtf.cxx
devaus120>> ./a.out
Called func() with a boolean arg

So, I have 2 questions:

  1. Is it a compiler bug that this compiles when the overloaded method is made const?
  2. How can I ensure the correct overloaded function gets invoked? (Need to cast the argument somehow?)

TIA.

Scott. :)

like image 605
Scott Smedley Avatar asked Oct 06 '22 18:10

Scott Smedley


1 Answers

Actually gcc is correct! Because lambda is not a function but a closure object of class type! Really! You can even inherit from it :) ... even multiple times from different lambdas...

So, according 8.5/16:


[...]

— If the destination type is a (possibly cv-qualified) class type:

[...]

Otherwise, if the source type is a (possibly cv-qualified) class type, conversion functions are considered. The applicable conversion functions are enumerated (13.3.1.5), and the best one is chosen through overload resolution (13.3). The user-defined conversion so selected is called to convert the initializer expression into the object being initialized. If the conversion cannot be done or is ambiguous, the initialization is ill-formed.


and 13.3.1.5:


Under the conditions specified in 8.5, as part of an initialization of an object of nonclass type, a conversion function can be invoked to convert an initializer expression of class type to the type of the object being initialized. Overload resolution is used to select the conversion function to be invoked. Assuming that “cv1 T” is the type of the object being initialized, and “cv S” is the type of the initializer expression, with S a class type, the candidate functions are selected as follows:

-- The conversion functions of S and its base classes are considered. Those non-explicit conversion functions that are not hidden within S and yield type T or a type that can be converted to type T via a standard conversion sequence (13.3.3.1.1) are candidate functions. For direct-initialization, those explicit conversion functions that are not hidden within S and yield type T or a type that can be converted to type T with a qualification conversion (4.4) are also candidate functions. Conversion functions that return a cv-qualified type are considered to yield the cv-unqualified version of that type for this process of selecting candidate functions. Conversion functions that return “reference to cv2 X” return lvalues or xvalues, depending on the type of reference, of type “cv2 X” and are therefore considered to yield X for this process of selecting candidate functions.


so finally, result of conversion function is a function pointer which would implicitly converted to bool...

you may check this series of conversions with the following simple code:

#include <iostream>
#include <iomanip>

int main()
{
    std::cout << std::boolalpha << []{ return 0; } << '\n';
}

the output will be true...

Here is few ways to workaround... you definitely need something because both functions are suitable after overload resolution. Btw, adding const, to signature of the second one, just exclude it because you've got a mutable instance of Scott and again you'll get compile error if declare it w/ const modifier.

So, you can do:

  • explicit cast (as mentioned in comments)... yeah, long to type...
  • declare the second foo w/ template parameter Func. Depending on what you are going to do then, here is few options: it can be converted to std::function on assign (if you want to store it to some member), or in case of immediate call you'll even get some optimization (by eliminate conversion to std::function)
  • more complex way is to declare both functions w/ template parameter and use std::enable_if to turn one of them OFF depending on std::is_same<bool, T> for example (or check for callable/function type)
  • use type dispatching (yeah, again w/ templated functions)

... I guess it's enough :)

like image 131
zaufi Avatar answered Oct 10 '22 04:10

zaufi