首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Clang中的歧义操作符过载

Clang中的歧义操作符过载
EN

Stack Overflow用户
提问于 2018-01-12 03:21:02
回答 2查看 648关注 0票数 15

请考虑以下几点:

代码语言:javascript
复制
template<typename T>
struct C {};
template<typename T, typename U>
void operator +(C<T>&, U);

struct D: C<D> {};

struct E {};
template<typename T>
void operator +(C<T>&, E);

void F() { D d; E e; d + e; }

这段代码在GCC-7和Clang-5上都编译得很好。为operator +选择的重载是struct E的重载。

现在,如果发生以下更改:

代码语言:javascript
复制
/* Put `operator +` inside the class. */
template<typename T>
struct C {
    template<typename U>
    void operator +(U);
};

也就是说,如果operator +是在类模板中定义的,而不是外部的,那么Clang会在代码中的两个operator +之间产生歧义。GCC仍然编译得很好。

这一切为什么要发生?这是GCC还是Clang的问题?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-01-12 23:45:27

这是gcc的一个bug,具体来说,是bug.cgi?id=53499

问题在于gcc将类模板成员函数的隐式对象参数视为依赖类型,即函数模板偏序gcc变换过程中的对象参数。

代码语言:javascript
复制
C<D>::template<class U> void operator+(U);  // #1

转到

代码语言:javascript
复制
template<class T, class U> void operator+(C<T>&, U);  // #1a (gcc, wrong)

当它应该被转化为

代码语言:javascript
复制
template<class U> void operator+(C<D>&, U);  // #1b (clang, correct)

我们可以看到,与你的

代码语言:javascript
复制
template<class T> void operator+(C<T>&, E);  // #2

#2比错误的#1a要好,但与#1b有歧义。

请注意,gcc甚至在C<D>根本不是模板时也不正确地接受--也就是说,当C<D>是类模板完全专门化时:

代码语言:javascript
复制
template<class> struct C;
struct D;
template<> struct C<D> {
    // ...

[temp.func.order]/3对此进行了介绍,并在示例中作了说明。请再次注意,gcc错误地编译了这个例子,错误地拒绝了它,但出于同样的原因。

票数 7
EN

Stack Overflow用户

发布于 2018-01-12 04:05:38

编辑:这个答案的原稿说GCC是对的。我现在相信,按照这个标准的措辞,Clang是正确的,但是我可以看到GCC的解释也是正确的。

让我们看一下您的第一个例子,其中两个声明是:

代码语言:javascript
复制
template<typename T, typename U>
void operator +(C<T>&, U);
template<typename T>
void operator +(C<T>&, E);

两者都是可行的,但很明显,第二个模板比第一个模板更专业。所以GCC和Clang都解决了对第二个模板的调用。但是让我们看看temp.func.order,看看为什么在标准的措辞中,第二个模板更专业。

偏序规则告诉我们用一个唯一的合成类型替换每个类型的模板参数,然后对另一个模板执行演绎。在此方案下,第一个重载类型将变为

代码语言:javascript
复制
void(C<X1>&, X2)

而对第二个模板的演绎失败,因为后者只接受E。第二种重载类型变成

代码语言:javascript
复制
void(C<X3>&, E)

并成功地对第一个模板进行了演绎(使用T = X3U = E)。由于演绎只在一个方向成功,接受另一个转换类型(第一个)的模板被认为不那么专业化,因此选择第二个重载作为更专门化的模板。

当第二个重载移到类C中时,仍然会发现两个重载,并且重载解析过程应该以完全相同的方式应用。首先,为两个重载构造参数列表,并且由于第一个重载是一个非静态类成员,因此插入了一个隐含的对象参数。根据over.match.funcs,该隐含对象参数的类型应该是"lvalue引用C<T>“,因为函数没有ref-限定符。所以这两个参数列表都是(C<D>&, E)。由于这无法在这两个重载之间进行选择,偏序测试再次启动。

在temp.func.order中描述的偏序测试还插入了一个隐含的对象参数:

如果只有一个函数模板M是某个类A的非静态成员,则M被认为在其函数参数列表中插入了一个新的第一个参数。给定cv作为M的cv -限定符(如果有的话),如果M的可选ref-限定符是&&,或者如果M没有ref-限定符,而其他模板的第一个参数具有rvalue引用类型,则新参数的类型为“rvalue引用到cvA”。否则,新参数类型为“lvalue引用cv A”。注意:这允许对一个非成员函数对一个非静态成员进行排序,其结果相当于两个等效的非成员的排序。-尾注

这一步大概是GCC和Clang对标准的不同解释。

我的看法:operator+成员已经在C<D>类中找到了。没有推导类T的模板参数C;它是已知的,因为名称查找过程进入了D的具体基类C<D>。因此,提交给偏序的实际operator+没有空闲的T参数;它不是void operator+(C<T>&, U),而是void operator+(C<D>&, U)

因此,对于成员重载,转换后的函数类型不应该是void(C<X1>&, X2),而应该是void(C<D>&, X2)。对于非成员重载,转换后的函数类型仍然是void(C<X3>&, E)。但是现在我们看到void(C<D>&, X2)不是非会员模板void(C<T>&, E)的匹配,void(C<X3>&, E)也不是成员模板void(C<D>&, U)的匹配。因此偏序失败,重载解析返回一个不明确的结果。

GCC决定继续选择非会员过载,如果你假设它是从词汇上为成员构造转换的函数类型,使它仍然是void(C<X1>&, X2),而Clang在开始偏序测试之前将D替换到模板中,只留下U作为一个自由参数,这是有意义的。

票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/48219096

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档