Gui*_*cot 8 c++ templates clang conversion-operator language-lawyer
我有一些代码使用模板转换运算符来查找通过ADL找到的函数的返回类型.
简化的代码如下所示:
#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& ();
    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;
};
namespace foo {
    struct bar {};
    auto find_me(bar const&) -> int { return 0; } 
}
int main() {
    // That would be inside a template in my code.
    find_me(probe<foo::bar>{});
}
在clang 6和GCC中,上面的代码编译.但是,在Clang 7中,它不再编译了!
正如你所看到的,clang 6解决了对call的调用,probe<foo::bar>::operator foo::bar&&<foo::bar, foo::bar&&, 0>()但是clang 7因为尝试调用而失败probe<foo::bar>::operator const foo::bar&&<const foo::bar, foo::bar&&, 0>()
哪个编译器是对的?标准中的规则是什么?这是一个新的Clang错误还是修复?
我想检查很多情况.不只是foo::bar作为参数,而是许多引用类型,例如:
namespace foo {
    struct bar {};
    auto find_me(bar const&) -> int { return 0; } 
    auto find_me(bar&&) -> int { return 0; } 
    auto find_me(bar const&&) -> int { return 0; } 
    auto find_me(bar&) -> int { return 0; } 
}
int main() {
    find_me(probe<foo::bar>{});
    find_me(probe<foo::bar&>{});
    find_me(probe<foo::bar&&>{});
    find_me(probe<foo::bar const&>{});
    find_me(probe<foo::bar const&&>{});
}
解决正确的函数调用很重要.
以下是所有这些案例的实例,GCC成功但clang失败:https://godbolt.org/z/yrDFMg
下面的简化示例代码说明了 clang 6/7 和 gcc 之间的行为差异:
\n\n#include <type_traits>\n\nstruct S{\n    template<class T,class=std::enable_if_t<!std::is_const_v<T>>>\n    operator T& ();\n};\n\nvoid test() {\n    S a;\n    const int& i = a; //Accepted by Gcc and clang 6 accept, rejected by clang 7\n}\nGcc 和 Clang 6 接受该代码,而 clang 7 拒绝它。
\n\n在 Gcc 的情况下,T=int和 都T=const int被视为案例。仅适用于 clang 7 T=const int。由于T=const int被禁用,clang 7 拒绝该代码。
\n\n\n考虑 S 及其基类的转换函数。\n 那些未隐藏在 S 中的非显式转换函数,并产生对 cv2 T2\xe2\x80\x9d 的类型 \xe2\x80\x9clvalue 引用(初始化左值时)对函数的引用或右值引用)或 \xe2\x80\x9ccv2 T2\xe2\x80\x9d 或 \xe2\x80\x9crvalue 对 cv2 T2\xe2\x80\x9d 的引用(当初始化右值引用或左值引用时函数),其中 \xe2\x80\x9ccv1 T\xe2\x80\x9d 与 \xe2\x80\x9ccv2 T2\xe2\x80\x9d 引用兼容,是候选函数。\n 对于直接初始化,那些显式转换未隐藏在 S 中的函数并产生类型 \xe2\x80\x9clvalue 对 cv2 T2\xe2\x80\x9d 的引用或 \xe2\x80\x9ccv2 T2\xe2\x80\x9d 或 \xe2\x80\x9crvalue 对 cv2 的引用T2\xe2\x80\x9d 也是候选函数,其中 T2 与 T 类型相同或可以通过限定转换转换为 T 类型。
\n
在我们的例子中,这意味着转换为S或int&可能const int&是候选者。
\n\n\n模板参数推导是通过将转换函数模板的返回类型(称为 P)与转换结果所需的类型(称为 A;参见 [dcl.init]、[over.match.conv ] 和 [over.match.ref] 用于确定该类型),如 [temp.deduct.type] 中所述。
\n
所以我认为两种字面读法是可以接受的:
\n\ngcc认为转换的结果并不代表转换序列的结果,所以它首先根据[over.match.ref]决定可以接受哪个转换序列,然后对所有的转换运算符进行模板参数推导可能的转换顺序。
clang 认为转换的结果确实意味着转换序列的目标。并且它仅对 执行参数推导T=cont int。
根据我在标准中读到的内容,我无法说出该标准的“正确”解释是什么。尽管如此,我认为 clang 行为总体上与模板参数推导更一致:
\n\ntemplate<class T,class=std::enable_if_t<std::is_const_v<T>>>\nvoid f(T& x);\n\nvoid test(){\n  int i;\n  f(i);\n  // If considering that the argument type is int caused\n  // template argument deduction failure, then template argument\n  // deduction would be performed for a const int argument.\n  // But template argument deduction succeeds. So T is deduced to int. \n  // Only after this deduction template argument substitution happens.\n  // => both gcc and clang reject this code.\n  }\n