临时/匿名对象的运算符查找是否不同?

Is operator lookup different for temporary/anonymous objects?

提问人:Tom_DB 提问时间:11/23/2022 更新时间:11/23/2022 访问量:60

问:

我试图理解为什么命名对象和临时对象(匿名?)对象在查找基类中定义的运算符时行为似乎不同。在下面的代码中,我围绕一个变量“mytype”制作了一个包装器,我想通过编译器定义在 double 和 std::complex 之间切换。对于这个包装器,我想添加一个使用基类型(类似于 boost::operators)的运算符。我的兴趣在于“main”中两条(诚然相当强迫)cout 线之间的区别。

#include <iostream>
#include <cmath>

#include <complex>

#ifdef COMPLEX
typedef std::complex<double> mytype;
#else
typedef double mytype;
#endif

template <typename DerivedType, typename integertype>
struct Base
{    
    friend DerivedType& operator +=(DerivedType& lhs, const integertype& rhs)
    {
        std::cout << "base += version" << std::endl;
        return lhs += mytype(rhs);
    }
};

struct Wrapper : public Base<Wrapper, unsigned>
{
    Wrapper(const mytype& rhs) : m_value(rhs) {}

    Wrapper(const unsigned& rhs) : Wrapper(mytype(rhs)) {}

    Wrapper& operator += (const Wrapper& rhs)
    {
        std::cout << "wrapper version" << std::endl;
        m_value += rhs.m_value;
        return *this;
    }

    Wrapper& operator += (const mytype& rhs)
    {
        std::cout << "wrapper mytype version" << std::endl;
        m_value += rhs;
        return *this;
    }
     
    mytype m_value;
};

int main()
{
    std::cout << (Wrapper(2.0) += 3u).m_value << std::endl;

    Wrapper t_MyWrapper(2.0);
    std::cout << (t_MyWrapper += 3u).m_value << std::endl;
}

如果我在没有-DCOMPLEX的情况下编译,我会得到以下输出:

wrapper mytype version
5
base += version
wrapper mytype version
5

据我所知,main 中的第一个输出忽略了 Base 的运算符+=(Wrapper&, const unsigned&)。相反,它将 unsigned 提升为 double(这优先于转换为 Wrapper),并调用运算符+=(Wrapper& const double&)。但是,“命名对象”确实从基类型调用运算符。使用 -DCOMPLEX 进行编译会导致编译错误:

SimplifiedProblem.cxx: In function ‘int main()’:
SimplifiedProblem.cxx:47:32: error: ambiguous overload for ‘operator+=’ (operand types are ‘Wrapper’ and ‘unsigned int’)
   47 |     std::cout << (Wrapper(2.0) += 3u).m_value << std::endl;
      |                   ~~~~~~~~~~~~~^~~~~
SimplifiedProblem.cxx:28:14: note: candidate: ‘Wrapper& Wrapper::operator+=(const Wrapper&)’
   28 |     Wrapper& operator += (const Wrapper& rhs)
      |              ^~~~~~~~
SimplifiedProblem.cxx:35:14: note: candidate: ‘Wrapper& Wrapper::operator+=(const mytype&)’
   35 |     Wrapper& operator += (const mytype& rhs)
      |              ^~~~~~~~

考虑到将 unsigned 转换为 std::complex 并不比转换为“Wrapper”(也不是标量类型)“更好”,编译错误是有道理的,但我想了解为什么不使用基类型的运算符。

将运算符+=(Wrapper&, const unsigned&) 直接移动到包装器可以避免此问题,删除构造函数 Wrapper(const unsigned&) 可以解决 -DCOMPLEX 时的歧义。但是我想知道临时对象查找的规则是否不同,或者是否还有其他原因导致了这种行为。

C++ 继承匿名 对象

评论

2赞 user17732522 11/23/2022
删除编译器选择的重载,它将首先告诉您剩余的重载是否可行。它会告诉你问题是什么,你不能将右值绑定到非左值引用。const
1赞 Richard Critten 11/23/2022
临时无法绑定,因此在 base 中声明的友元运算符不是候选者。然后,编译器尝试标准转换,并可以提升为双精度。DerivedType& lhs3u
0赞 user17732522 11/23/2022
这与尝试没有什么不同.void f(int&) {}; int main() { f(1); }
0赞 Tom_DB 11/23/2022
谢谢大家的回答,确实我没有考虑过这一点。所以没有办法通过基类在 Wrapper 中添加 operator+=(const unsigned&) 的等效项?反正我并不严格需要它,但现在至少我明白发生了什么。
0赞 user17732522 11/23/2022
@Tom_DB 通过 a 在基类中的等价物是两个重载,一个接受引用,一个接受非引用。但是,这并不能解决歧义问题。friendconstconst

答:

0赞 bitmask 11/23/2022 #1

文本隐式转换为 a,因此编译器标记给您的行中的两个重载都是可能的候选者。3uWrapperoperator+=

一个简单的解决方案是用显式标记两个构造函数。然后,它们永远不会允许隐式转换为 a,但始终要求您键入转换,就像在第一行中所做的那样。WrapperWrappermain