在 C++17 中重载命名空间和子命名空间中的运算符是不明确的

Overloading an operator in a namespace and a sub-namespace in C++17 is ambiguous

我尝试在命名空间中重载 operator<<。此外,我想在第一个命名空间中包含一个调试命名空间,其中 operator<< 做的更多。

在主函数中,我在第一个命名空间中创建了一个class的对象,并用std::cout给出了它。我希望在我可以这样做之前必须 'name' 运算符,如 using test::operator<<,但我不必这样做。

这导致了我的问题: 如果我现在想使用我的调试运算符,它是模棱两可的,我不能使用它,我真的不明白为什么。

#include <iostream>
#include <string>

namespace test{
    class A{
        std::string str_;
    public:
        explicit A(const std::string& str) : str_{str} {}
        inline std::ostream& toStream(std::ostream& os) const {
            return os << str_ << "\n";
        }
    };
    std::ostream& operator<< (std::ostream& os, const A& a) {
        return a.toStream(os);
    }
}

namespace test {
    namespace debug {
        std::ostream& operator<< (std::ostream& os, const A& a) {
            std::string info = "\n\tDebug\n"
                "\t\tLine: " + std::to_string(__LINE__) + "\n"
                "\t\tFile: " __FILE__ "\n"
                "\t\tDate: " __DATE__ "\n"
                "\t\tTime: " __TIME__ "\n"
                "\t\tVersion: " + std::to_string(__cplusplus) + "\n";
            return a.toStream(os) << info;
        }
    }
}

int main(int argc, const char* argv[]) {
    test::A a{"Test"};
    if(argc > 1) {
        using test::debug::operator<<;
        // Ambiguous error
        std::cout << a << "\n";
    } else {
        // Don't need it for some reason
        // using test::operator<<;
        std::cout << a << "\n";
    }
}

I expected to have to 'name' the operator before I can do that, as in using test::operator<<, but I don't have to.

这是因为Argument Dependent Lookup (ADL).

如果您通过 using 将运算符从 debug 命名空间拉入当前范围,这不会“覆盖”现有运算符,它们都是可用的,因此会产生歧义。

有很多方法可以处理它,一种可能是使用不同类型的调试输出:

namespace test {
    namespace debug {
        struct debug_A {
            const A& data;
            debug_out(const A& a) : a(a) {}
        };

        std::ostream& operator<< (std::ostream& os, const debug_A& d) {
            auto& a = d.data;
            std::string info = "\n\tDebug\n"
                "\t\tLine: " + std::to_string(__LINE__) + "\n"
                "\t\tFile: " __FILE__ "\n"
                "\t\tDate: " __DATE__ "\n"
                "\t\tTime: " __TIME__ "\n"
                "\t\tVersion: " + std::to_string(__cplusplus) + "\n";
            return a.toStream(os) << info;
        }
    }
}

现在您可以通过

调用它
std::cout << test::debug::debug_A{ a } << '\n';

当你有:

using test::debug::operator<<;
std::cout << a << "\n";

查找 std::cout << a 将找到两个候选项:

  • test::debug::operator<<(ostream&, A const&)通过常规不合格查找,通过using-declaration找到。
  • test::operator<<(ostream&, A const&) 通过依赖于参数的查找 (ADL),因为 A 在命名空间 test 中,所以我们在那里找到候选人。

这两个候选人的签名相同,根本没有什么区别,所以是模棱两可的。


在我看来,最明智的做法实际上是包装 a。写入:

std::cout << debug{a} << '\n';

其中 debug 只是一个类型,它具有对 A 的成员引用,并且具有比平常更详细的自定义日志记录。