Search code examples
c++c++11templatesoverload-resolutionexplicit-conversion

Priority and ambiguity of explicit conversion operator templates


I've been playing around with templated explicit conversion operators in my project, to implement explicit conversion from custom variant-like type. The minimal example reproducing my problem looks like the following (in C++14 mode):

#include <iostream>
#include <stdexcept>
#include <cmath>

using namespace std;

class A
{
        public:
        template<typename T> explicit operator T() const // 1
        {
                cout << "operator T" << endl;
                return T();
        }

        template<typename T> explicit operator const T&() const // 2
        {
                cout << "operator const T&" << endl;
                throw runtime_error("operator const T&");
        }

        template<typename T> explicit operator T&() // 3
        {
                cout << "operator T&" << endl;
                throw runtime_error("operator T&");
        }


};


int main(int, char**)
{
        try
        {
                const A& a = A();
                cout << abs(static_cast<double>(a) - 3.14) << endl;
        }
        catch (const runtime_error&)
        {

        }

        return 0;
}

The problem I faced is the operator chosen for the static_cast conversion. With the GCC it's sort of expected (1) case. The output is:

operator T
3.14

But Clang refuses to compile this with the following output:

main.cpp:37:20: error: ambiguous conversion for static_cast from 'const A' to 'double'
            cout << std::abs(static_cast<double>(a) - 3.14) << endl;
                             ^~~~~~~~~~~~~~~~~~~~~~
main.cpp:10:32: note: candidate function [with T = double]
    template<typename T> explicit operator T() const
                                  ^
main.cpp:16:32: note: candidate function [with T = double]
    template<typename T> explicit operator const T&() const
                                  ^
1 error generated.

Why Clang considers conversion (2), when it apparently would require additional constructor call in the conversion sequence, while (1) wouldn't? And is it right (and GCC is then wrong) doing so?


Solution

  • static_cast performs either implicit conversion or direct initialisation. In your case, implicit conversion is not viable, but direct initialisation is because static_cast considers explicit constructors and conversion functions. So my guess is that clang (in my opinion correctly) identifies that there are two possible direct intialisations and complains accordingly. Not sure what is going on inside GCC, maybe it defaults to operator T() if it can find one, regardless of whether other ones exist.