【问题标题】:Overloading an operator in a namespace and a sub-namespace in C++17 is ambiguous在 C++17 中重载命名空间和子命名空间中的运算符是不明确的
【发布时间】:2020-06-24 13:26:58
【问题描述】:

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

在 main 函数中,我在第一个命名空间中创建一个类的对象,并使用 std::cout 将其分发出去。我希望必须先“命名”操作员,然后才能这样做,例如 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";
    }
}

【问题讨论】:

  • 你能详细说明为什么你认为它没有模棱两可吗?它们具有完全相同的签名。
  • 我以为我必须明确使用using test::operator&lt;&lt;; 告诉编译器在哪里找到重载,因为它隐藏在命名空间“test”中。
  • 额外的调试数据不会给你太多。 __FILE____LINE__ 将始终是定义 operator&lt;&lt; 的文件。我假设您想获取您调用的行和文件?
  • 哦,我不知道。是,对的。我认为预处理器使用了调用它的文件和行。
  • 不,很遗憾没有。这就是日志库通常使用宏的原因,例如:#define LOG(msg) std::cout FILE LINE

标签: c++ namespaces operator-overloading c++17 overloading


【解决方案1】:

当你有:

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

查找std::cout &lt;&lt; a 将找到两个候选人:

  • test::debug::operator&lt;&lt;(ostream&amp;, A const&amp;) 通过常规非限定查找,通过 using-declaration 找到。
  • test::operator&lt;&lt;(ostream&amp;, A const&amp;) 通过参数相关查找 (ADL),因为 A 在命名空间 test 中,所以我们在那里找到候选对象。

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


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

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

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

【讨论】:

  • 非常感谢您的回答。 @idclev 463035818 的代码示例帮助了我一点点,这就是为什么我将他的回复标记为解决方案,但你的也很好。我只需要选择一个。
  • 在全局命名空间中使用using 和定义operator&lt;&lt; 有什么区别?当移动到全局命名空间时,不再有错误。 godbolt.org/z/CTDkzE
  • @MikaelH 这里只进行常规的、不合格的查找。我们从最里面到最外面的范围,直到我们找到一个候选人,然后我们停下来。所以一旦你找到test::debug::operator&lt;&lt;,你就不要继续前进了。 ADL 什么也没找到。
  • 哦,所以它与 namespaces 没有任何关系。如果我们调用using,符号被拉进入那个范围,并且它会被明确地找到? (就像在 { int a = 3; std::cout &lt;&lt; a; // 3} 范围内重新定义全局变量 int a = 2;
【解决方案2】:

我希望必须先“命名”操作员,然后才能这样做,就像使用 test::operator

这是因为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';

【讨论】:

    猜你喜欢
    • 2010-09-15
    • 1970-01-01
    • 2018-06-27
    • 1970-01-01
    • 1970-01-01
    • 2011-12-03
    • 1970-01-01
    • 1970-01-01
    • 2010-10-20
    相关资源
    最近更新 更多