首页 > 解决方案 > 为 std::strings 重载 operator+ - 一个坏主意?

问题描述

现在通常应该稀疏地使用运算符重载 - 特别是当它涉及 stdlib 时。

尽管我很好奇陷阱可能是什么(如果有的话),除了读者可能无法清楚地看到代码中发生了什么的明显缺陷之外 - 是否有任何技术原因可以避免这种特殊的重载?

std::string operator+(const std::string& lhs, const std::wstring& rhs) {
    return lhs + to_utf8(rhs);
}

(也有做反向变换的双重重载)

我发现这可以使一些操作更容易写出来,例如:

std::wstring{L"hel"} + "lo " + getName();

有什么优点和缺点,特别是您是否看到任何可能“适得其反”的场景(技术场景)?

性能不是问题。

标签: c++operator-overloadingc++17

解决方案


您不应该这样做,因为它会破坏参数相关查找 (ADL)。

考虑这个无辜的测试代码:

namespace myNamespace
{
    struct MyType {};

    MyType operator+(MyType, MyType);

    template<class T>
    auto doSomething(T t)
    {
        return t + std::wstring{};
    }
}

看起来没有问题,是吗?

嗯,它是:

void test()
{
    std::string s;
    myNamespace::doSomething(s);
}
error: invalid operands to binary expression ('std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >' and 'std::wstring' (aka 'basic_string<wchar_t>'))
        return t + std::wstring{};
               ~ ^ ~~~~~~~~~~~~~~

<source>:25:18: note: in instantiation of function template specialization 'myNamespace::doSomething<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >' requested here
    myNamespace::doSomething(s);
                 ^

<source>:12:12: note: candidate function not viable: no known conversion from 'std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >' to 'myNamespace::MyType' for 1st argument
    MyType operator+(MyType, MyType);
           ^

https://godbolt.org/z/bLBssp

问题是operator+找不到模板的定义。模板中的operator+是通过不合格的名称查找来解决的。这基本上做了两件事:

  1. 在每个封闭范围内递归查找任何 operator+内容,直到找到第一个。operator+如果您为std::string和在全局范围内或在不同的名称空间中定义自己的名称,那么当模板有任何“更接近”std::wstring时,就无法以这种方式找到它。 operator+

  2. 查看与运算符 (ADL) 的参数类型相关的命名空间。由于这两种类型都来自namespace std,我们在那里查看并发现没有任何operator+工作(请参阅godbolt 上的其他错误说明)。您不能将自己的运算符放在那里,因为这是未定义的行为。

因此,经验法则:仅重载涉及您的类型的运算符,因为该运算符必须与您的类型放在相同的命名空间中才能使 ADL 工作。


即使没有模板,问题也是一样的,但在这种情况下,手动拉入操作员可能是合理的。要求通用代码(甚至可能不是你的)显然是不合理的。


推荐阅读