模板转换运算符的解析度不明确

时间:2018-06-24 20:47:50

标签: c++ templates language-lawyer conversion-operator

我必须执行类似的代码:

#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 and ClangVisual 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

1 个答案:

答案 0 :(得分:1)

代码可以简化为the following

#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] / 56

  

通常,推论过程会尝试查找使推论的A与A相同的模板参数值。但是,有四种情况允许不同:

     
      
  • 如果原始A是引用类型,则A可以比推导的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<...>部分,则在Clang编译时MSVC和GCC失败。为了进一步分析,我重点介绍第一个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射击only under Clang。也就是说,Clang推论Tsome_type而不是const some_type。我认为这是Clang的错误。