使用 std::variant 的重载 << 函数时出现问题

3

我有一个针对别名为std::variantA::Var)的重载<<,同时我在不同命名空间C::Wrapper中定义了一个模板函数,该函数只是将其参数转发到std::ostream

我正在尝试从A中定义的另一个函数A::Foo中调用它,但这给我带来了编译器错误。以下是这个玩具示例。

#include <iostream>
#include <variant>

namespace C {
  struct Wrapper {
    template<typename T>
    auto& operator<<(T&& v) {
      std::cout << std::forward<T>(v);
      return *this;
    }
  };
}

namespace A {
  using Var = std::variant<bool, int>;

  auto& operator<<(std::ostream& os, const A::Var& v) {
    std::visit([&os](auto i) { os << i; }, v);
    return os;
  }

  struct Foo {
    void m() {
      C::Wrapper wrap;
      Var v{3};
      wrap << "hi"; // works
      wrap << v; // compiler error
    }
  };
}

int main() { 
  A::Foo a;
  a.m();
}

g++ -std=c++17 出现以下错误:

main.cpp: In instantiation of ‘auto& C::Wrapper::operator<<(T&&) [with T = std::variant<bool, int>&]’:
main.cpp:27:11:   required from here
main.cpp:8:15: error: no match for ‘operator<<’ (operand types are ‘std::ostream’ {aka ‘std::basic_ostream<char>’} and ‘std::variant<bool, int>’)
    8 |     std::cout << std::forward<T>(v);

... many candidate functions none including my overloaded << for A::Var

我原本期望它能够成功编译并在运行时打印出3。我尝试将所有定义都放在类外,移除const限定符,将重载的<<函数设为全局,但这些方法都没有奏效。

我该如何修复这个错误,同时保留命名空间和类结构呢?


更新:

通过在全局命名空间中定义<<,可以使代码在gcc下编译,但在clang下会失败:

namespace A {
  using Var = std::variant<bool, int>;
}

auto &operator<<(std::ostream &os, const A::Var &v) {
  std::visit([&os](auto i) { os << i; }, v);
  return os;
}

namespace A {
  struct Foo {
    void m() {
      C::Wrapper wrap;
      A::Var v{3};
      wrap << "hi"; // works
      wrap << v;    // compiler error
    }
  };
} // namespace A

clang++ -std=c++17 出现错误:

main.cpp:7:15: error: call to function 'operator<<' that is neither visible in the template definition nor found by argument-dependent lookup
    std::cout << std::forward<T>(v);
              ^
main.cpp:28:10: note: in instantiation of function template specialization 'C::Wrapper::operator<<<std::variant<bool, int> &>' requested here
    wrap << v;    // compiler error
         ^
main.cpp:17:7: note: 'operator<<' should be declared prior to the call site
auto &operator<<(std::ostream &os, const A::Var &v) {
      ^
1 error generated.

为什么它在gcc下能工作,但是在clang下却不行?

2
Var 只是一个别名,而不是独立的类型。你正在调用 C::Wrapper::operator<<(const std::variant<bool, int> &), 由于在该调用中没有引用 namespace A,因此 ADL 不知道要在 namespace A 中查找重载的 operator<<。它只会考虑 C::Wrappernamespace C 和全局命名空间中的运算符。 - Remy Lebeau
1
@RemyLebeau我发现如果我在全局命名空间中定义variant的重载<<,它可以在gcc中使用,但在clang中失败。这可能是一个好的解决方案,但我确实需要它在clang中工作。有任何想法为什么? - Albert
2
不看到您的更新代码和实际错误信息,无法确定答案。 - Remy Lebeau
1
@RemyLebeau 我更新了问题,加上了代码和错误信息。 - Albert
2
@Albert,GCC接受您的编辑更改是一个错误,已在GCC 12中修复,请参见https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51577。自定义`ostream`的唯一正确方法是拥有一个实际的自定义类`X`和一个`operator<<重载,它们位于完全相同的命名空间中,该命名空间属于X。对于std::`类型的自定义无法正确执行。 - user17732522
1个回答

4
Argument-dependent lookup (ADL)不考虑与别名关联的命名空间。(这也没有意义,编译器应该记住所有具有到std::variant<bool,int>的别名的命名空间,然后突然将其中声明的所有函数视为属于std::variant<bool,int>吗?)
Var成为一个真正的独立类型。要么将其变为带有私有std::variant<bool,int>成员的类,然后您可以实现和转发需要使用的std::variant接口的部分,要么让Var公开继承std::variant<bool,int>并继承构造函数,这样std::visit仍然可以直接使用。
然后,当您将<<应用于Var时,ADL将考虑名称空间A中的operator<<重载。

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