Simple stringifying using << operator Background

I wrote my_length class and some code. my_length can be stringified using &lt;&lt; operator, and main() tests this.

Questions


I think operator&lt;&lt; can be refactored. How can I rewrite operator&lt;&lt; of my_length, and make it better code?
How can I make rest of code better?


Code

#include &lt;vector&gt;
#include &lt;string&gt;
#include &lt;cstdint&gt;
#include &lt;iostream&gt;

class my_length {
  int id;
  std::string name;
  uint64_t v;     // unit is 'mm'
  bool fmt_auto;

  public:
  my_length (int id, std::string name, uint64_t v)
    : id{id}, name{name}, v{v}, fmt_auto(false) {}
  friend std::ostream &amp;operator&lt;&lt;(std::ostream &amp;out, const my_length &amp;l) {
    out &lt;&lt; l.id &lt;&lt; "/" &lt;&lt; l.name &lt;&lt; " : ";
    if (!l.fmt_auto) {
        return out &lt;&lt; l.v &lt;&lt; "mm";
    } else {
        if (l.v&lt;10)
            return out &lt;&lt; l.v &lt;&lt; "mm";
        else if (l.v&lt;1000)
            return out &lt;&lt; double(l.v)/10 &lt;&lt; "cm";
        else if (l.v&lt;1000000)
            return out &lt;&lt; double(l.v)/1000 &lt;&lt; "m";
        else
            return out &lt;&lt; double(l.v)/1000000 &lt;&lt; "km";
    }
  }
  void set_fmt_auto(bool b) {fmt_auto = b;}
};

bool need_fmt_auto(int x) {
  return (x%2) != 0;
}

int main() {
  std::vector&lt;my_length&gt; lengths;
  my_length l{0, "a", 1};
  lengths.push_back(l);
  uint64_t val = 17;
  for (int i=1; i&lt;10; i++) {
    my_length l{i, std::string(1, 'a'+i), val};
    lengths.push_back(l);
    val *= 10;
  }
  for (int i=0; i&lt;lengths.size(); i++) {
    if (need_fmt_auto(i)) {
        lengths[i].set_fmt_auto(true);
        std::cout &lt;&lt; "fmt_auto: ";
    }
    std::cout &lt;&lt; lengths[i] &lt;&lt; "\n";
  }
}
I see a number of things that may help you improve your program.

Gather constants together

The relationship among the numerical values that determine which unit is used and the divisor and the actual unit name is contained within a single function, which is good, but it could be more clear if expressed as a structure.  For example, one could use this:

struct Units {
  int limit;
  double divisor;
  std::string name;
};
static const std::array&lt;Units,4&gt; units; 


Then outside of the class, we supply the values:

const std::array&lt;my_length::Units,4&gt; my_length::units = {{
  { 10, 1, "mm" },
  { 1000, 10, "cm" },
  { 1000000, 1000, "m" },
  { 0, 1000000, "km" },
}};


This brings things into much closer proximity so that it can be more easily seen and understood.  We can do a little better with C++17 by using std::string_view instead of std::string for the unit name.

Isolate concerns

It's usually better to have each function just do one thing.  So insted of deciding on units and also printing, I'd suggest splitting those into two, using a private member function:

const Units&amp; selectUnit() const {
  auto ret{units.cbegin()};
  if (fmt_auto) {
      while (ret-&gt;limit &amp;&amp; v &gt;= ret-&gt;limit) {
          ++ret; 
      }
  }
  return *ret;
}


Now the operator&lt;&lt; looks like this:

friend std::ostream &amp;operator&lt;&lt;(std::ostream &amp;out, const my_length &amp;l) {
  const Units u{l.selectUnit()};
  return out &lt;&lt; l.id &lt;&lt; "/" &lt;&lt; l.name &lt;&lt; " : " 
      &lt;&lt; l.v/u.divisor &lt;&lt; u.name;
}


Use standard algorithms

The test code starts with this:

std::vector&lt;my_length&gt; lengths;
my_length l{0, "a", 1};
lengths.push_back(l);
uint64_t val = 17;
for (int i=1; i&lt;10; i++) {
    my_length l{i, std::string(1, 'a'+i), val};
    lengths.push_back(l);
    val *= 10;
}


I'd suggest that one could use std::iota instead.  I'll leave it to you to create the details of that.

Reconsider the design

Once constructed, there is no capability to do anything with the my_length object except to print it.  If all that's needed is printing, then it may be better to use a freestanding function in a namespace instead of an object, as suggested in the comments.  If not, then it makes sense to provide those other operations within the class.