在命名空间中使用 lambda 时未找到运算符重载

4
以下代码在 Clang 5.0.0 / gcc 7.3 中无法编译(标准:C++11):
Clang 的错误信息:
错误:二进制表达式的操作数无效(std::vector<double, std::allocator<double> >std::vector<double, std::allocator<double>>)。
#include <functional>
#include <vector>

namespace ns{

using MyType = std::vector<double>;

} // namespace ns

using ns::MyType;
MyType& operator+=( MyType& lhs, const MyType& rhs) {
  for (int i = 0; i < lhs.size(); ++i) {
    lhs[i] = lhs[i] + rhs[i];
  }
  return lhs;
}
MyType operator+( MyType lhs, const MyType& rhs) {
  lhs += rhs;
  return lhs;
}

namespace ns{

using Func = std::function<MyType()>;

Func operator+(
    const Func &lhs, const Func &rhs) {
  return [lhs, rhs]() {
    auto y = lhs() + rhs(); // <-- error in this line
    return y;
  };
}

} // namespace ns

编译器无法找到正确的 operator+ 重载。我不明白为什么。我在命名空间外定义运算符的原因是ADL对于typedef和使用类型别名无法起作用。这里的问题是什么?为什么编译器不能在上述情况下找到 operator+(MyType, const MyType &)
以下所有替代方案都可以编译:
namespace ns {

MyType a, b;
auto c = a + b; // compiles

MyType f() {
    MyType a_, b_;
    return a_ + b_; // compiles
};

Func operator+(
    const Func &lhs, const Func &rhs) {
  return [lhs, rhs]() {
    auto x = lhs();
    x += rhs(); // <-- compiles; operator+= instead of operator+
    return x;
  };
}

} // namespace ns

Func operator+(
    const Func &lhs, const Func &rhs) {
  return [lhs, rhs]() {
    auto y = lhs() + rhs(); // <-- no error if not in namespace ns
    return y;
  };
}

@user463035818 我想使用的operator+是针对MyType的,它是调用Func实例的结果。代码应该是lhs() + rhs(),而不是lhs + rhs。我有什么遗漏吗? - user2296653
1
抱歉,我漏掉了一些东西。请在问题中包含错误信息。 - 463035818_is_not_a_number
@JiveDadson 是的:“这里有什么问题?”,意思是为什么这不能编译?为什么编译器在上述情况下找不到 operator+(MyType, const MyType &)?我更新了问题以更清楚地阐明这一点。 - user2296653
这里有一个有趣的小知识:这个可以编译,但是添加一个无关的operator+就会出问题... - Max Langhof
1
@JiveDadson 显然他想知道什么使它非法。我不认为示例大小有问题,它是完整的、可验证的,并且对他分享的所有观察都是最小的。 - Max Langhof
2个回答

4
您正在隐藏全局命名空间中的operator+,因为您已在当前作用域中定义了一个operator+。未限定名称查找会一直向封闭的命名空间移动,直到找到至少一个声明为止。因此,由于当前命名空间中存在一个operator+,无论其参数列表如何,名称查找都不会继续搜索全局命名空间。有关限定查找的详细说明,请参见此处。相关部分位于顶部。

对于未限定名称,即不出现于作用域解析运算符::右侧的名称,名称查找将按照下面描述的作用域进行检查,直到找到至少一个任意类型的声明,此时查找停止,不再检查其他作用域。

请注意这两个示例是如何工作的。只显示更改的部分,您的其余代码必须仍然可见才能编译。

在未限定名称查找中显式包含::operator++

namespace ns {
    using Func = std::function<MyType()>;
    using ::operator+;

    Func operator+(
        const Func &lhs, const Func &rhs) {
        return [lhs, rhs]() {
            auto y = lhs() + rhs();
            return y;
        };
    }

} // namespace ns

确保没有函数隐藏全局的operator+

namespace ns {
    using Func = std::function<MyType()>;

    Func func(
        const Func &lhs, const Func &rhs) {
        return [lhs, rhs]() {
            auto y = lhs() + rhs();
            return y;
        };
    }

} // namespace ns

3
这是因为ADL不会自动搜索封闭的命名空间,除非它是一个内联命名空间:

cppreference

如果与类和命名空间相关的集合中有任何一个命名空间是内联命名空间,则其封闭的命名空间也会被添加到该集合中。

考虑以下代码:
namespace N {
    namespace M {
        struct foo {};
    }

    void operator+(M::foo, M::foo) {}
}


int main()
{
    N::M::foo f;
    f + f; // Error, enclosing namespace N won't be searched.
}

同样地,在您的情况下,相关的命名空间是std,封闭的全局命名空间不会被ADL考虑。
实际上,您代码中的operator+是通过未限定名称查找找到的,只要找到一个名称就会停止查找。当operator+namespace ns中声明时,首先找到该运算符,而全局命名空间中的运算符将不会被搜索。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接