Неоднозначное разрешение с оператором преобразования шаблонов

Я должен был сделать подобный код:

#include <type_traits>

template<typename S>
struct probe {
    template<typename T, typename U = S, std::enable_if_t<
        std::is_same<T&, U>::value &&
        !std::is_const<T>::value, int> = 0>
    operator T& () const;

    template<typename T, typename U = S&&, std::enable_if_t<
        std::is_same<T&&, U>::value &&
        !std::is_const<T>::value, int> = 0>
    operator T&& ();

    template<typename T, typename U = S, std::enable_if_t<
        std::is_same<T const&, U>::value, int> = 0>
    operator T const& () const;

    template<typename T, typename U = S&&, std::enable_if_t<
        std::is_same<T const&&, U>::value, int> = 0>
    operator T const&& () const;
};

struct some_type {};
struct other_type {};

auto test_call(some_type const&, other_type) -> std::false_type;
auto test_call(some_type&, other_type) -> std::true_type;

int main() {
    static_assert(decltype(test_call(probe<some_type&>{}, other_type{}))::value, "");
}

Он работает под GCC и Clang, но не компилируется в visual studio с неоднозначной ошибкой разрешения. Какой компилятор не так и почему?

GCC и Clang, Визуальная студия

Вот вывод msvc:

source_file.cpp(31): error C2668: 'test_call': ambiguous call to overloaded function
source_file.cpp(28): note: could be 'std::true_type test_call(some_type &,other_type)'
source_file.cpp(27): note: or       'std::false_type test_call(const some_type &,other_type)'
source_file.cpp(31): note: while trying to match the argument list '(probe<some_type &>, other_type)'
source_file.cpp(31): error C2651: 'unknown-type': left of '::' must be a class, struct or union
source_file.cpp(31): error C2062: type 'unknown-type' unexpected

1 ответ

Решение

Код может быть сокращен до следующего:

#include <type_traits>

struct some_type {};

struct probe {
    template<typename T, std::enable_if_t<!std::is_const<T>::value, int> = 0>
    operator T& () const;
};

auto test_call(some_type const&) -> std::false_type;
auto test_call(some_type&) -> std::true_type;

int main() {
    static_assert(decltype(test_call(probe{}))::value, "");
}

Согласно [temp.deduct.conv] / 5 & 6:

В общем, процесс вывода пытается найти значения аргументов шаблона, которые сделают вывод A идентичным A. Однако, есть четыре случая, которые допускают разницу:

  • Если исходный A является ссылочным типом, A может быть более квалифицированным по cv, чем выведенный A (т. Е. Тип, на который ссылается ссылка)

  • ...

Эти альтернативы рассматриваются только в том случае, если в противном случае вычет типа не удался. Если они дают более одного возможного вывода A, вывод типа не выполняется.

T выводится some_type для обоих вызовов функций. Тогда согласно [over.ics.rank] / 3.3:

Определяемая пользователем последовательность преобразования U1 является лучшей последовательностью преобразования, чем другая определенная пользователем последовательность преобразования U2, если они содержат одну и ту же определяемую пользователем функцию или конструктор преобразования или они инициализируют один и тот же класс в совокупной инициализации и в любом случае вторая стандартная последовательность преобразования U1 лучше, чем вторая стандартная последовательность преобразования U2.

probe -> some_type& -> some_type& лучше, чем probe -> some_type& -> const some_type&Так что двусмысленности нет, GCC и Clang правы.


Кстати, если мы удалим std::enable_if_t<...> В приведенном выше коде MSVC и GCC не работают, пока Clang компилируется. Для дальнейшего анализа я остановлюсь на первом test_all:

#include <type_traits>

struct some_type {};

struct probe {
    template<typename T>
    operator T& () const
    {
        static_assert(std::is_const_v<T>);
        static T t;
        return t;
    }
};

auto test_call(some_type const&) -> std::false_type;

int main() {
    test_call(probe{});
}

Тогда мы находим static_assert стреляет только под Clang. То есть Clang выводит T быть some_type вместо const some_type, Я думаю, что это ошибка Clang.

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