C++ 用户定义的转换运算符优先级,以g++;但不是叮当作响++;

C++ 用户定义的转换运算符优先级,以g++;但不是叮当作响++;,c++,templates,c++11,operator-overloading,C++,Templates,C++11,Operator Overloading,我有以下代码,它是POD到模板类Foo的包装,其中T是包装类型(可以是int,double等)。我定义了一个模板转换操作符,以及友元加法操作符+,请参见下面的代码。 在main()的最后一行中,我定义了fooa=10然后计算 cout << 2.1 + a << endl; // outputs 12 然后变成operator+(Foo(2.1),a)。为什么编译器不先将a转换为double,然后执行加法?也就是说,为什么不将表达式计算为 2.1 + a.operato

我有以下代码,它是POD到模板类
Foo
的包装,其中
T
是包装类型(可以是
int
double
等)。我定义了一个模板转换操作符,以及友元加法
操作符+
,请参见下面的代码。 在
main()
的最后一行中,我定义了
fooa=10
然后计算

cout << 2.1 + a << endl; // outputs 12
然后变成
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 // 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
}
#包括
使用名称空间std;
样板
类Foo//POD的包装器类
{
T val;//这是包装的值
公众:
Foo(T val={}):val(val){};
模板//转换运算符
运算符S()
{
STD::CUT< P>我没有手动的工具,但是C++更喜欢通过对象的类型化来隐式地对对象进行类型化。换句话说,它将解释<代码>双+FoO < /代码>为<代码> FO(double)+Foo<代码>,如果<代码> FoO 具有一个构造函数,可以使用<代码>双< /代码>。(“可以使用代码< >双< /代码>”允许将
double
隐式类型转换为其他内容,如
int
,除非所述构造函数声明为
explicit

如果<代码> FoO 没有合适的构造函数,那么它会考虑调用<代码> fo::运算符双()/代码>将对象降级为<代码>双< /代码>…我甚至不确定语言甚至会暗中尝试!

如果您确实想使
double+Foo
首先将
Foo
转换为
double
,然后添加,您需要编写:

double operator +(double a, const Foo<int>& b)
{
    return a + double(b);
}
double运算符+(双a,常数Foo&b)
{
返回a+double(b);
}
<>或某种模板等价物。不需要代码>朋友< /COD>声明,只要<代码> fo::运算符双()/代码>存在。

< P>我不习惯手工操作,但C++更喜欢通过对象的类型化来隐式地对对象进行类型化。换句话说,它将解释<代码>双+FoO < /代码>为<代码> FO(double)。+Foo
如果
Foo
有一个构造函数可以使用
double
(“可以使用
double
”允许将
double
隐式类型转换为
int
等其他内容,除非所述构造函数声明为
explicit

如果<代码> FoO 没有合适的构造函数,那么它会考虑调用<代码> fo::运算符双()/代码>将对象降级为<代码>双< /代码>…我甚至不确定语言甚至会暗中尝试!

如果您确实想使
double+Foo
首先将
Foo
转换为
double
,然后添加,您需要编写:

double operator +(double a, const Foo<int>& b)
{
    return a + double(b);
}
double运算符+(双a,常数Foo&b)
{
返回a+double(b);
}

或者某种类似的模板。只要存在
Foo::operator double()
,就不需要
friend
声明。

让我们从§13.3.1.2[over.match.oper]/p2-3开始:

如果任一操作数的类型为类或枚举,则 可以声明用户定义的运算符函数来实现此 运算符或用户定义的转换可能是转换 将操作数转换为适用于内置运算符的类型 在这种情况下,重载解析用于确定哪个运算符函数 或调用内置运算符来实现该运算符

[……]

对于二进制运算符
@
,其左操作数类型为 cv非限定版本是
T1
,并且是类型为 cv不合格版本为
T2
,三套候选函数, 指定成员候选人、非成员候选人和内置候选人 候选者的结构如下:

  • 如果T1是完整的类类型或当前正在定义的类,则候选成员集是
    T1::operator@
    (13.3.1.1.1);否则,成员候选集为 空的
  • 非成员候选集是根据 非限定函数调用中名称查找的常用规则(3.4.2) 除了忽略所有成员函数之外。[…]
  • 对于运算符
    、一元运算符
    &
    、或运算符
    ->
    ,内置候选集为空。对于所有其他运算符 内置候选函数包括所有候选运算符函数 在13.6中定义,与给定的运算符相比,
    • 具有相同的操作员名称,并且
    • 接受相同数目的操作数,并且
    • 接受给定操作数可根据其转换为的操作数类型 13.3.3.1,以及
    • 不具有与任何非函数模板专用化的非成员候选项相同的参数类型列表
因此,给定表达式
2.1+a
,让我们构造候选集:

  • T1
    double
    ,不是类类型,因此成员候选集为空
  • 非成员候选集包括:

    Foo<int> operator+(Foo<int> lhs, const Foo<int>& rhs);
    
    (我只列出了第一个参数的类型为
    double
    ,因为这是第一个参数的类型,因此其他内置参数不可能比任何一个更好。
    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)
    
        operator+(double, int);  // built-in
        Foo<int> operator+(Foo<int> lhs, const Foo<int>& rhs); // overload