模板转换运算符的模糊分辨率

Gui*_*cot 6 c++ templates conversion-operator language-lawyer

我不得不做类似的代码:

#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, "");
}
Run Code Online (Sandbox Code Playgroud)

它在GCC和Clang下工作,但它不能在visual studio上编译,并且有一个模糊的分辨率错误.哪个编译器错了,为什么?

GCC和Clang,Visual studio

这是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
Run Code Online (Sandbox Code Playgroud)

xsk*_*xzr 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, "");
}
Run Code Online (Sandbox Code Playgroud)

根据 [temp.deduct.conv]/ 5 & 6

一般来说,推导过程会尝试找到使推导的 A 与 A 相同的模板参数值。但是,有四种情况允许存在差异:

  • 如果原始 A 是引用类型,则 A 可以比推导的 A 更具 cv 限定性(即引用引用的类型)

  • ...

仅当类型推导失败时才考虑这些替代方案。如果它们产生多个可能的推导 A,则类型推导失败。

T推断适用some_type于两个函数调用。然后根据 [over.ics.rank]/ 3.3

如果用户定义的转换序列 U1 包含相同的用户定义的转换函数或构造函数,或者它们在聚合初始化中初始化相同的类,并且在任一情况下都是第二个标准转换序列,则用户定义的转换序列 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{});
}
Run Code Online (Sandbox Code Playgroud)

然后我们发现static_assert只在 Clang 下。也就是说,Clang 推导T为 besome_type而不是const some_type。我认为这是 Clang 的一个错误。