8
\$\begingroup\$

I have a strongly typed enum and std::vector of this type.

enum class Colors { red, green, blue };
std::vector v = { Colors::blue, Colors::red };

I trying to output v to standard output via std::copy.

std::copy(v.begin(), v.end(), std::ostream_iterator<Colors>(std::cout, " "); 

Compilation failed because compiler do not know how to print Colors. I can define operator << for colors, but it looks too excessive for me. Then try print int values of v elements.

std::copy(v.begin(), v.end()
  , std::ostream_iterator<std::underlying_type<Colors>::type>>(std::cout, " "); 

Compilation failed because it is prohibited to print Colors to std::ostream_iterator<int>. Ok. Then std::transform can help.

std::transform(v.begin(), v.end()
,   std::ostream_iterator<std::underlying_type<Colors>::type>(std::cout, " ")
,   [&](Colors &c) -> std::underlying_type<Colors>::type
    {
        return std::underlying_type<Colors>::type(c);
    }
);

However, this lambda looks ugly. Is there neater way to copy or transform vector of Colors? Maybe boost has some way?

Of course, a loop can print, but I'd prefer to avoid explicit loop.

for(auto c : v)
{
    std::cout << std::underlying_type<Colors>::type(c) << std::endl;
}

I have a lot of different enum classes in my project. All of them I need to print out. I don't like idea to create operator << for each one individually. However, if I try to make templated operator << then I've got a global redefinition of this operator. And this is not what I actually want.

template <typename T>
std::ostream &operator << (std::ostream &_os, const T &_t) {
    _os << typename std::underlying_type<T>::type(_t);
    return _os;
}
\$\endgroup\$
  • \$\begingroup\$ Why do you want to avoid defining an operator<< or using a range-based for loop? \$\endgroup\$ – Yuushi Mar 11 '14 at 13:08
  • \$\begingroup\$ I avoid defining an operator<< because I do not want to multiply entities, if it possible. If it's not possible, then I will use the operator. I avoid loop because I like more "functional" style. \$\endgroup\$ – Loom Mar 11 '14 at 13:29
10
\$\begingroup\$

It seems to me that if you want to insert an object into a stream, the "right" way to do it is normally to define an operator<< for that type. That goes for strongly typed enums just as much as it does for class/struct objects.

#include <iostream>
#include <vector>
#include <utility>
#include <iterator>

enum class colors { red, green, blue };
std::vector<colors> v = { colors::blue, colors::red };

std::ostream &operator<<(std::ostream &os, colors const &c) {
    return os << std::underlying_type<colors>::type(c);
}

int main() {
    std:copy(v.begin(), v.end(), std::ostream_iterator<colors>(std::cout, "\n"));
}

That seems simple and straightforward enough that I'd be at least a little surprised to see a substantially cleaner/simpler solution (at least in C++ as it's currently defined; obviously, it's open to argument that some other language or some future definition of C++ could make things cleaner).

Using something like std::transform puts the burden in the wrong place. It requires that all code that uses the type in question contain and encode "knowledge" of the internals of the enumeration. What you generally want is to contain the complexity and the internals of the type in one place, so the rest of the code can just work with its external interface. An external interface of stream << object is simple and straightforward. Forcing the rest of the world to work with an interface of stream << std::underlying_type<colors>::type(c); anything but straightforward or simple. It's ugly and nasty.

\$\endgroup\$
3
\$\begingroup\$

You can do this using SFINAE to fail instantiating your global operator<< overload for non-enums:

template<typename EnumT>
typename std::enable_if<std::is_enum<EnumT>::value, std::ostream>::type &operator<<(std::ostream &os, EnumT x) {
    return os << typename std::underlying_type<EnumT>::type(x);
}

Full, working source, including an abi demangle to pretty-print the enums:

#include <iostream>
#include <iterator>
#include <type_traits>
#include <typeinfo>
#include <cxxabi.h>
#include <memory>
#include <cstdlib>
#include <vector>

template<typename EnumT>
typename std::enable_if<std::is_enum<EnumT>::value, std::ostream>::type &operator<<(std::ostream &os, EnumT x) {
    int status;
    std::unique_ptr<char, void(*)(void*)> demangled_enum(abi::__cxa_demangle(typeid(EnumT).name(), NULL, NULL, &status), std::free);
    std::unique_ptr<char, void(*)(void*)> demangled_underlying(abi::__cxa_demangle(typeid(typename std::underlying_type<EnumT>::type).name(), NULL, NULL, &status), std::free);
    os << demangled_enum.get() << ":" << demangled_underlying.get() << "(" << typename std::underlying_type<EnumT>::type(x) << ")";
    return os;
}

enum EnumA { a, b, c };
enum class EnumB : short { a, b, c };

int main() {
    std::cout << 3 << std::endl;
    std::cout << EnumA::b << std::endl;
    std::cout << EnumB::c << std::endl;
    std::cout << "foo" << std::endl;
    char bar[] = "bar";
    std::cout << bar << std::endl;
    char *baz = bar;
    std::cout << baz << std::endl;
    std::cout << std::string(baz) << std::endl;

    std::vector<EnumB> v = { EnumB::a, EnumB::c };
    std::copy(v.begin(), v.end(), std::ostream_iterator<EnumB>(std::cout, "\n"));
    return 0;
}

Output:

3
EnumA:unsigned int(1)
EnumB:short(2)
foo
bar
bar
bar
EnumB:short(0)
EnumB:short(2)

The various non-enum << calls are just there to show that our overload is not being called for them. It took me a while to get this to work -- I had my overload called for non-const strings as I was working on it. It beats me why this doesn't work when std::enable_if is used on the EnumT x argument, or for partial instantiation of a helper struct (all of which I tried) -- I'll leave that to somebody else.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.