Why don't the any_cast function overloads cause ambiguity?

212 views Asked by At

Boost's <boost/any.hpp> has:

template<typename ValueType>
ValueType any_cast(any & operand);

template<typename ValueType>
inline ValueType any_cast(const any & operand);

(among other variants.) Shouldn't this combination cause ambiguity in calls such as boost::any_cast<int>(my_any); ?

I'm asking because if I write this program:

#include <boost/any.hpp>
#include <iostream>

template<typename ValueType>
ValueType any_cast(boost::any & operand)
{
        return boost::any_cast<ValueType>(operand);
}

int main()
{
        int x = 123;
        boost::any my_any(x);
        std::cout << "my_any = " << any_cast<int>(my_any) << "\n";
        return 0;
}

I do get a complaint about ambiguity:

g++ -std=c++14 -O3 -Wall -pedantic -pthread main.cpp && ./a.out
main.cpp: In function 'int main()':
main.cpp:14:57: error: call of overloaded 'any_cast(boost::any&)' is ambiguous
         std::cout << "my_any = " << any_cast<int>(my_any) << "\n";
                                                         ^
main.cpp:5:11: note: candidate: ValueType any_cast(boost::any&) [with ValueType = int]
 ValueType any_cast(boost::any & operand)
           ^~~~~~~~
In file included from main.cpp:1:0:
/usr/local/include/boost/any.hpp:281:22: note: candidate: ValueType boost::any_cast(const boost::any&) [with ValueType = int]
     inline ValueType any_cast(const any & operand)
                      ^~~~~~~~
/usr/local/include/boost/any.hpp:258:15: note: candidate: ValueType boost::any_cast(boost::any&) [with ValueType = int]
     ValueType any_cast(any & operand)
               ^~~~~~~~
1

There are 1 answers

4
Dietmar Kühl On

Why would the calls be ambiguous? The way you call the function the any argument is an lvalue. Thus, the any argument will either be const-qualified in which case the second overload is the only potential match or it is not const-qualified in which case the first overload is the better match (there is no conversion needed while the second overload would need a conversion from any& to any const&). If you call the function with a temporary any, it could bind to an rvalue overload (i.e., taking any&&) or, if that doesn't exist, it can bind to the const-qualified overload but not the non-const-qualified overload, again, not causing any ambiguity.

Actually, there is something interesting happening here: without the overload in the global namespace the function using the explicit template argument cannot be used! However, as soon as any function template is present, even a non-matching one, it can be used! Here is an example:

namespace foo {
    struct bar {};

    template <typename T> void bar_cast(bar&) {}
    template <typename T> void bar_cast(bar const&) {}
    template <typename T> void bar_cast(bar&&) {}
}

struct whatever;
template <typename T> void bar_cast(whatever);

int main()
{
    foo::bar b;
    bar_cast<int>(b);
}