Приоритет и неоднозначность шаблонов операторов явного преобразования

Я играл с шаблонными операторами явного преобразования в моем проекте, чтобы реализовать явное преобразование из пользовательского варианта-подобного типа. Минимальный пример, воспроизводящий мою проблему, выглядит следующим образом (в режиме C++14):

#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;
}

Проблема, с которой я столкнулся, - это оператор, выбранный для преобразования static_cast. С GCC это своего рода ожидаемый (1) случай. Выход:

operator T
3.14

Но Clang отказывается компилировать это со следующим выводом:

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.

Почему Clang рассматривает преобразование (2), когда ему, очевидно, потребуется дополнительный вызов конструктора в последовательности преобразования, а (1) - нет? И правильно ли (а GCC тогда неправильно) делает это?

1 ответ

Решение

static_cast выполняет неявное преобразование или прямую инициализацию. В вашем случае неявное преобразование нежизнеспособно, но прямая инициализация static_cast рассматривает явные конструкторы и функции преобразования. Таким образом, я предполагаю, что clang (на мой взгляд, правильно) идентифицирует, что есть две возможные прямые инициализации, и жалуется соответственно. Не уверен, что происходит внутри GCC, возможно, по умолчанию operator T() если он может найти один, независимо от того, существуют ли другие.

Другие вопросы по тегам