Search code examples
c++operator-overloadingc++17iostreamostream

Are there better ways to overload ostream operator<<?


Suppose that you have the following code:

#include <iostream>

template <typename T>
class Example
{
  public:
    Example() = default;
    Example(const T &_first_ele, const T &_second_ele) : first_(_first_ele), second_(_second_ele) { }

    friend std::ostream &operator<<(std::ostream &os, const Example &a)
    {
      return (os << a.first_ << " " << a.second_);
    }

  private:
    T first_;
    T second_;
};

int main()
{
  Example example_(3.45, 24.6); // Example<double> till C++14
  std::cout << example_ << "\n";
}

Is this the only way to overload the operator<<?

friend std::ostream &operator<<(std::ostream &os, const Example &a)
{
  return (os << a.first_ << " " << a.second_);
}

In terms of performance, is it the best way to overload it or are there better options to do this implementation?


Solution

  • I believe that the comments have answered your question well enough. From a pure performance standpoint, there likely is no "better" way to overload the << operator for output streams because your function is likely not the bottleneck in the first place.

    I will suggest that there is a "better" way to write the function itself that handles some corner cases.

    Your << overload, as it exists now, will 'break' when trying to perform certain output formatting operations.

    std::cout << std::setw(15) << std::left << example_ << "Fin\n";
    

    This does not left align your entire Example output. Instead it only left aligns the first_ member. This is because you put your items in the stream one at a time. std::left will grab the next item to left align, which is only a part of your class output.

    The easiest way is to build a string and then dump that string into your output stream. Something like this:

    friend std::ostream &operator<<(std::ostream &os, const Example &a)
    {
        std::string tmp = std::to_string(a.first_) + " " + std::to_string(a.second_);
        return (os << tmp);
    }
    

    It's worth noting a few things here. The first is that in this specific example, you will get trailing 0's because you don't get any control over how std::to_string() formats its values. This may mean writing type-specific conversion functions to do any trimming for you. You may also be able to use std::string_views (to gain back some efficiency (again, it likely doesn't matter as the function itself is probably still not your bottleneck)), but I have no experience with them.

    By putting all of the object's information into the stream at once, that left-align will now align the full output of your object.

    There is also the argument about friend vs. non-friend. If the necessary getters exist, I would argue that non-friend is the way to go. Friends are useful, but also break encapsulation since they are non-member functions with special access. This gets way into opinion territory, but I don't write simple getters unless I feel that they are necessary, and I don't count << overloads as necessary.