【问题标题】:Template conversion operator difference between clang 6 and clang 7clang 6 和 clang 7 的模板转换运算符区别
【发布时间】:2018-10-04 17:06:21
【问题描述】:

我有一些代码使用模板转换运算符来查找通过 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 中,它不再编译了!

https://godbolt.org/z/Lfs3UH

如您所见,clang 6 将调用解析为 probe&lt;foo::bar&gt;::operator foo::bar&amp;&amp;&lt;foo::bar, foo::bar&amp;&amp;, 0&gt;(),但 clang 7 失败,因为它尝试调用 probe&lt;foo::bar&gt;::operator const foo::bar&amp;&amp;&lt;const foo::bar, foo::bar&amp;&amp;, 0&gt;()

哪个编译器是正确的?标准中的规则是什么?这是新的 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

【问题讨论】:

  • 奇怪,但如果你将 std::is_same&lt;T const&amp;&amp;, U&gt; 更改为 std::is_same&lt;T&amp;&amp;, U&gt; 它将起作用。我认为这个 C++ 17 特性。 godbolt.org/z/ZO7pfC
  • @VictorGubin 是的,但这有点作弊,在这种情况下不会起作用:godbolt.org/z/0q4WOP
  • 哦,那可能是另一个clang错误? godbolt.org/z/USjQPD
  • 我删除了答案,因为我真的对 [temp.deduct.conv] 感到困惑并且不知道如何应用这些规则,它们可能是相关的。

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


【解决方案1】:

以下简化的示例代码说明了 clang 6/7 和 gcc 之间的行为差​​异:

#include <type_traits>

struct S{
    template<class T,class=std::enable_if_t<!std::is_const_v<T>>>
    operator T& ();
};

void test() {
    S a;
    const int& i = a; //Accepted by Gcc and clang 6 accept, rejected by clang 7
}

Gcc 和 Clang 6 接受代码,而 clang 7 拒绝它。

在 Gcc 的情况下,T=intT=const int 都被视为案例。仅适用于 clang 7 T=const int。因为T=const int被禁用,所以clang 7拒绝代码。

根据[over.match.ref]

考虑了 S 及其基类的转换函数。 那些未隐藏在 S 中并产生类型“对 cv2 T2 的左值引用”(初始化对函数的左值引用或右值引用时)或“cv2 T2”或“对 cv2 T2 的右值引用”(当初始化对函数的右值引用或左值引用),其中“cv1 T”与“cv2 T2”引用兼容,是候选函数。 对于直接初始化,那些不隐藏在 S 中的显式转换函数分别产生类型“lvalue reference to cv2 T2”或“cv2 T2”或“rvalue reference to cv2 T2”,其中 T2 与 T 或可以通过限定转换转换为 T 类型,也是候选函数。

在我们的例子中,这意味着将S 转换为int&amp;const int&amp; 可能是候选对象。

还有[temp.deduct.conv]

模板参数推导是通过将转换函数模板的返回类型(称为 P)与作为转换结果所需的类型(称为 A;参见 [dcl .init]、[over.match.conv] 和 [over.match.ref] 用于确定该类型),如 [temp.deduct.type] 中所述。

所以我认为两种字面意思是可以接受的:

  1. gcc认为转换的结果并不代表转换序列的结果,所以首先根据哪个转换序列来决定可接受的[over.match.ref] 然后对所有可能的转换序列的转换运算符进行模板参数推导。

  2. clang 认为 转换的结果 确实意味着 转换序列的目标。而且它只对T=cont int进行参数推导。

根据我在标准中读到的内容,我无法说出对标准的“正确”解释。尽管如此,我认为 clang 行为通常更符合模板参数推导:

template<class T,class=std::enable_if_t<std::is_const_v<T>>>
void f(T& x);

void test(){
  int i;
  f(i);
  // If considering that the argument type is int caused
  // template argument deduction failure, then template argument
  // deduction would be performed for a const int argument.
  // But template argument deduction succeeds. So T is deduced to int. 
  // Only after this deduction template argument substitution happens.
  // => both gcc and clang reject this code.
  }

【讨论】:

    【解决方案2】:

    我相信这与Bug 32861original report 有关。 似乎已经在clang 7 中解决了。

    以第二次转换重载为例:

    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&& ();
    

    clang 6 中扣除T 将是T=bar 导致 std::is_same&lt;T&amp;&amp;, U&gt;::value 是真的,但在 clang 7 中,扣除将是 T=bar const,现在该特征不再成立, 重载不会添加到候选集中。

    还要注意,在clang 7 中,扣除是T=bar const 也会导致!std::is_const&lt;T&gt;::value 为假,并有助于丢弃过载。

    【讨论】:

    • 呃?这是自 clang 7 以来的回归
    • 不,这是一个修复
    猜你喜欢
    • 1970-01-01
    • 2020-12-16
    • 2021-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-02
    • 2012-12-01
    • 1970-01-01
    相关资源
    最近更新 更多