Why is it not possible to override operator<< for template classes involving third-party code?
Asked Answered
C

2

6

I asked about the following in https://mcmap.net/q/1774854/-using-equality-operators-with-boost-optional :

I want to overload template<class T> ostream& operator<<(ostream& os, const optional<unique_ptr<T>>&).

In the comments, @Yakk - Adam Nevraumont notes:

The answer to that question is "you cannot". There is no good legal way to do that for a generic type T; I could explain why, but it would take a new question/answer to do so

I'm creating a new Q. to take up the kind offer...

Chinoiserie answered 21/8, 2018 at 18:11 Comment(2)
Standard library is not "third-party code" in my dictionary.Ocher
@Ocher I meant boost, not std.Chinoiserie
S
8

The proper place to overload operators in in the namespace associated with the type.

For std::optional<std::unique_ptr<T>> there is one associated namespace std that is always there (from ostream and optional and unique_ptr), plus whatever namespace is associated with T. As you want to overload for all types, the namespace(s) associated with T are not useful to you.

It is not legal to introduce a new function or overload into std; in certain limited circumstances you can introduce specializations, but none of them apply here. Adding a new overload of << to std makes your program ill formed, no diagnostic required.

You could (A) use decorated unique_ptr or optionals from your own namespace, or (B) use a decorated ostream, or (C) write a formatter wrapper:

namespace fmt {
  template<class T>
  struct wrapper_t {
    T&& t;
  };
  template<class T>
  struct is_wrapped:std::false_type{};
  template<class T>
  struct is_wrapped<wrapper_t<T>>:std::true_type{};

  template<class OS, class T,
    std::enable_if_t<!is_wrapped<OS&>{}, bool> =true
  >
  auto operator<<( OS& os, wrapper_t<T>&& w )
  -> decltype( os << std::forward<T>(w.t) )
      { return os << std::forward<T>(w.t); }
  template<class OS, class T>
  auto operator<<( wrapper_t<OS&> os, T&& t )
  -> decltype( os.t << std::forward<T>(t) )
      { return os.t << std::forward<T>(t); }

  template<class T>
  wrapper_t<T> wrap(T&& t){ return {std::forward<T>(t)}; }
}

then std::cout << fmt::wrap( foo ) can find overloads of << within fmt, and if none are found invokes << on the contained data.

This also supports fmt::wrap(std::cout) instead of wrapping the arguments. There are probably typos.

Surgeon answered 21/8, 2018 at 18:27 Comment(2)
OP seems to be using boost::optional and tried to define the operator in boost, but it still didn't work. Can you also explain this please? Thanks! :)Weisbrodt
@Weisbrodt It is possible because boost is disabling ADL; I've seen boost code that exists only to disable ADL using some hacks. I don't know the details, and would have to debug the specific case.Surgeon
B
0

In addition to what has been said, there's another issue involving ODR problems. If you overload a function for types you don't control, it is conceivable that someone else also overloads it differently. Then you compile your code with your overload, they compile their code with their overload, and when you work for the same company you end up with multiple versions of the same-signature function in different translation units. Again, undefined behavior, no diagnostic required.

Sanitizers may find this, and perhaps it sounds contrived, but such things happen from time to time.

Burnight answered 21/8, 2018 at 19:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.