用户定义的转换运算符优先级,以g ++而不是clang ++编译 [英] user-defined conversion operators precedence, compiles in g++ but not clang++

查看:153
本文介绍了用户定义的转换运算符优先级,以g ++而不是clang ++编译的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下代码,它是一个POD的封装到模板类 Foo< T> ,其中 T 是包装类型(可以 int double 等)。我定义了一个模板化的转换操作符,还有朋友添加 operator + ,见下面的代码。
main()的最后一行,我定义 Foo< int> a = 10 then compute

  cout< 2.1 + a < endl; // outputs 12 

这里的规则是什么?看起来表达式正在翻译为

  operator +(2.1,a)

这将成为 operator +(Foo (2.1),a)。为什么编译器不尝试首先将 a 转换为 double ,然后执行添加?也就是说为什么我们没有将表达式计算为

  2.1 + a.operator double()

谢谢!



PS:只是意识到 clang ++ 无法编译代码,说对 operator + 的重载调用是不明确的。然而 g ++ 4.9 即使已打开所有警告标志,也会毫无问题地进行编译。



  #include< iostream> 
using namespace std;

template< typename T>
class Foo // POD的包装类
{
T val_; //这是包装的值
public:
Foo(T val = {}):val_(val){};

template< typename S> // conversion operator
operator S()
{
std :: cout< 调用转换运算符< std :: endl;
return val_;
}

// + = operator
Foo& operator + =(const Foo& rhs)
{
val_ + = rhs.val_;
return * this;
}

// + operator
friend Foo operator +(Foo lhs,const Foo& rhs)
{
cout< Calling operator +<< endl;
return lhs + = rhs;
}

// stream operator
friend std :: ostream& operator<<<(std :: ostream& os,const Foo& rhs)
{
return os<< rhs.val_;
}
};

int main()
{
Foo< int> a = 10;

// operator +(2.1,a),为什么不
// 2.1 + a。 operator int()?
cout<< 2.1 + a < endl; // outputs 12
}


解决方案

从§13.3.1.2[over.match.oper] / p2-3:


如果任一操作数的类型是类,枚举,
用户定义的运算符函数可能被声明为实现这个
运算符,或者用户定义的转换可能是必要的,以将
操作数转换为适合于构建-in运算符。在这个
情况下,重载决议用于确定调用哪个操作符函数
或内置操作符来实现该操作符。



[...]



为一个二进制运算符 @ cv非限定版本是 T1 和一个类型的右操作数,其
cv-unqualified版本 T2 ,三组候选函数,
指定的成员候选,非成员候选和内置的
候选,被构造如下:




  • 如果T1是一个完整的类类型或当前正在定义的类,那么成员候选集合是合格查找
    T1 :: operator @ (13.3.1.1.1);

  • 非成员候选者集合是对 operator @ 的无限制查找的结果, code>在根据
    的表达式的上下文中通常用于在非限定函数调用(3.4.2)
    中的名称查找规则,除了忽略所有成员函数。 [...]

  • 对于运算符,一元运算符& 或运算符 - > ,内置候选集为空。对于所有其他运算符,
    内置候选包括在13.6中定义的所有候选运算符函数
    ,与给定运算符相比,

    • 具有相同的运算符名称,并且

    • 接受相同数量的操作数,

    • 接受可以转换给定操作数的操作数类型根据
      13.3.3.1和

    • 不具有与不是函数模板专用化的任何非成员候选者相同的参数类型列表。



2.1 + a ,我们构建候选集:




  • T1 double ,而不是类类型,所以成员候选集为空。

  • 成员候选集包括:

      Foo< int>运算符+(Foo  lhs,const Foo & rhs); 

    (加上许多其他重载用于 Foo 这明显是比这更糟糕的匹配。)


  • 内置候选集包括一个长列表的函数,你可以看到在clang的输出对于您的代码,在§13.6[over.built] / p12中指定:


    对于每对提升的算术类型 $ R ,存在
    候选运算符函数[...] (L,R);

    [...]其中 LR 是通常的算术转换结果
    between L R





只有在这批候选人中找到唯一的最佳匹配项,重载解析才能成功。



首先,注意下面的许多可能的内置操作符,没有一个可能是唯一的最佳匹配,因为 Foo 可以转换为每个可能的类型的权利操作数:

 操作符+(double,unsigned long long)
操作符+ (double,unsigned int)
运算符+(double,__int128)
运算符+(double,long long)
运算符+(double,long)
运算符+ b运算符+(double,double)
运算符+(double,long double)
运算符+(double,int)

(我只列出了第一个参数是 double 的类型,因为这是第一个参数的类型,因此其他内建函数可以)



因此,重载解析可以成功,如果且仅当你的重载 operator + 是比它们中的每一个更好的匹配。不失一般性,我们考虑以下两个函数:

  operator +(double,int); // built-in 
Foo< int>运算符+(Foo lhs,const Foo & rhs); //重载
(double,Foo< int>)时,

。对于第一个候选项,第一个参数是精确匹配,第二个参数需要用户定义的转换。对于第二个候选项,第一个参数需要用户定义的转换,第二个参数是精确匹配。



因此,我们有一个十字交叉的情况,这意味着两个候选函数都不比另一个更好。 (一个函数F1比另一个F2更好的第一个要求是对于每个参数,F1所需的转换不会比F2-§13.3.3[over.match.best] / p2更差。)



结果,没有唯一的最佳过载,重载解决失败,程序是不成形的。 Clang拒绝此代码是正确的,而g ++则无法拒绝它。


I have the following code, which is a wrapper for POD into a template class Foo<T>, where T is the wrapped type (can be int, double etc). I define a templated conversion operator, and also the friend addition operator+, see code below. In the last line of main(), I define Foo<int> a = 10 then compute

cout << 2.1 + a << endl; // outputs 12

What is the rule here? It looks like the expression is being translated as

operator+(2.1, a)

which then becomes operator+(Foo<int>(2.1), a). Why doesn't the compiler try to first convert a into a double then perform the addition? I.e. why don't we have the expression evaluated as

2.1 + a.operator double()

Thanks!

PS: Just realized that clang++ fails to compile the code, saying that overloaded call to operator+ is ambiguous. However g++4.9 compiles it without problems, even with all warning flags turned on.

Code snippet below:

#include <iostream>
using namespace std;

template <typename T>
class Foo // wrapper class for a POD
{
    T val_; // this is the wrapped value
public:
    Foo(T val = {}): val_(val) {};

    template<typename S> // conversion operator
    operator S ()
    {
        std::cout << "Calling conversion operator" << std::endl;
        return val_;
    }

    // the += operator
    Foo& operator+=(const Foo& rhs)
    {
        val_ += rhs.val_; 
        return *this;
    }

    // the + operator
    friend Foo operator+(Foo lhs, const Foo& rhs)
    {
        cout << "Calling operator+" << endl;
        return lhs += rhs;
    }

    // stream operator
    friend std::ostream &operator<<(std::ostream &os, const Foo &rhs)
    {
        return os << rhs.val_;
    }
};

int main()
{
    Foo<int> a = 10;

    // operator+(2.1, a), why not
    // 2.1 + a. operator int() ?
    cout << 2.1 + a << endl; // outputs 12
}

解决方案

Let's start from §13.3.1.2 [over.match.oper]/p2-3:

If either operand has a type that is a class or an enumeration, a user-defined operator function might be declared that implements this operator or a user-defined conversion can be necessary to convert the operand to a type that is appropriate for a built-in operator. In this case, overload resolution is used to determine which operator function or built-in operator is to be invoked to implement the operator.

[...]

for a binary operator @ with a left operand of a type whose cv-unqualified version is T1 and a right operand of a type whose cv-unqualified version is T2, three sets of candidate functions, designated member candidates, nonmember candidates and built-in candidates, are constructed as follows:

  • If T1 is a complete class type or a class currently being defined, the set of member candidates is the result of the qualified lookup of T1::operator@ (13.3.1.1.1); otherwise, the set of member candidates is empty.
  • The set of non-member candidates is the result of the unqualified lookup of operator@ in the context of the expression according to the usual rules for name lookup in unqualified function calls (3.4.2) except that all member functions are ignored. [...]
  • For the operator ,, the unary operator &, or the operator ->, the built-in candidates set is empty. For all other operators, the built-in candidates include all of the candidate operator functions defined in 13.6 that, compared to the given operator,
    • have the same operator name, and
    • accept the same number of operands, and
    • accept operand types to which the given operand or operands can be converted according to 13.3.3.1, and
    • do not have the same parameter-type-list as any non-member candidate that is not a function template specialization.

So, given the expression 2.1 + a, let's construct the candidate sets:

  • T1 is double, not a class type, so the set of member candidates is empty.
  • The non-member candidate set consists of:

    Foo<int> operator+(Foo<int> lhs, const Foo<int>& rhs);
    

    (plus lots of other overloads for different instantiations of Foo that's obviously a worse match than this one.)

  • The built-in candidate set consists of a long list of functions you can see in clang's output on your code, specified in §13.6 [over.built]/p12:

    For every pair of promoted arithmetic types L and R, there exist candidate operator functions of the form [...] LR operator+(L , R ); [...] where LR is the result of the usual arithmetic conversions between types L and R.

Overload resolution can succeed only if a unique best match can be found among this pile of candidates.

First, note that of the numerous possible built-in operators below, none can possibly be the unique best match, because Foo<int> is convertible to every possible type of the right operand:

operator+(double, unsigned long long)
operator+(double, unsigned long)
operator+(double, unsigned int)
operator+(double, __int128)
operator+(double, long long)
operator+(double, long)
operator+(double, float)
operator+(double, double)
operator+(double, long double)
operator+(double, int)

(I only listed ones whose first argument is of type double, since that's the type of the first argument, and hence the other built-ins can't possibly be better than any of those.)

Thus, overload resolution can succeed if and only if your overload of operator + is a better match than every one of them. Without loss of generality, we consider the following two functions:

    operator+(double, int);  // built-in
    Foo<int> operator+(Foo<int> lhs, const Foo<int>& rhs); // overload

given an argument list of (double, Foo<int>). For the first candidate, the first argument is an exact match, the second one requires a user-defined conversion. For the second candidate, the first argument requires a user-defined conversion, and the second one is an exact match.

We thus have a criss-cross situation, which means that neither candidate function is better than the other. (The first requirement of one function F1 being better than the other F2 is that for each argument the conversion required for F1 is not worse than F2 - §13.3.3 [over.match.best]/p2.)

As a result, there's no unique best overload, overload resolution fails, and the program is ill-formed. Clang is correct in rejecting this code, and g++ is broken in failing to reject it.

这篇关于用户定义的转换运算符优先级,以g ++而不是clang ++编译的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆