Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Templatize stream operator for printing enumerations

Tags:

c++

templates

I have a namespace that creates a series of enums used through my application with associated print statements, like so:

namespace NS {
    enum EnumA { A1, A2, A3 };
    enum EnumB { B1, B2, B3 };

    inline std::string toString(const EnumA key) {
        switch(key) {
        case A1: return "A1";
        case A2: return "A2";
        case A3: return "A3";
        }
        return "UNKNOWN";
    }

    // .. same for EnumB
}

// this is the part I would like to templatize
inline std::ostream& operator<<(std::ostream& s, const NS::EnumA key) {
    s << NS::toString(key);
    return s;
}
inline std::ostream& operator<<(std::ostream& s, const NS::EnumB key) {
    s << NS::toString(key);
    return s;
}

Is it possible to templatize the stream operator to work with any NS enum so I only have to have one? Like:

template <typename T>
inline std::ostream& operator<<(std::ostream& s, const NS::<T> key) {
    s << NS::toString(key);
    return s;
}
like image 645
steveo225 Avatar asked Jan 01 '26 00:01

steveo225


2 Answers

If the only things in namespace NS are toString-capable types, you can put your operator<< in the namespace, thusly:

namespace NS {
  enum EnumA { A1, A2, A3 };
  enum EnumB { B1, B2, B3 };

  inline std::string toString(const EnumA key) {
    ...
  }
  inline std::string toString(const EnumB key) {
    ...
  }

  template <typename T>
  inline std::ostream& operator<<(std::ostream& s, const T key) {
    std::operator << (s, NS::toString(key));
    return s;
  }

}

A complete program is here.

like image 160
Robᵩ Avatar answered Jan 02 '26 14:01

Robᵩ


The obvious approach will result in your overload being considered for all types; this will result in calls being ambiguous for all types, since templates are not choosy. So you need some way to tell the compiler that your enum is the right type of enum, and to ignore all the rest; a traits class is probably the easiest way to do that.

namespace NS {
  enum EnumA { };

  template<typename T>
  struct is_ns_enum : std::false_type { };

  template<>
  struct is_ns_enum<EnumA> : std::true_type { };
}

From there, you can use SFINAE to implement your function.

template<typename T>
inline typename std::enable_if<is_ns_enum<T>::value, std::ostream&>::type
operator <<(std::ostream& s, const T&) {
  ...
}

This way, the overload is considered for any types you specialize is_ns_enum for, but is discarded for all other types, preventing that slew of ambiguous overload errors.

like image 21
Dennis Zickefoose Avatar answered Jan 02 '26 12:01

Dennis Zickefoose