Fmt: enum class support

Created on 28 Sep 2016  路  7Comments  路  Source: fmtlib/fmt

Hello - this library is a godsend. Thank you. I'm working in a project that uses a number of enum classes, e.g:

enum class ErrorCode : int {
SOME_ERROR;
}

I'm interested in formatting these objects as ints, but the built-in support wants to treat them as custom objects. I can't change these classes to add ostream support. What is the easiest way to do this? I tried adding a templatized report_arg overload, which works if I add it to the C file generating the log statement, but not if I add it the header from which I'm including format.h

template::value >::type>
void format_arg(fmt::BasicFormatter &f, const char *, const T &e) {
f.writer() << (int)e;
}

Any suggestions would be very appreciated.

Dave

All 7 comments

Similar question is how best to add formatting support for a templated type like std::optional from the standard library that doesn't support the << operator.

Thanks again.

I'm glad you like the library =).

To format a enum class you'll have to provide an overloaded operator<< because there is no implicit conversion to int there:

enum class ErrorCode : int {
  SOME_ERROR
};

std::ostream &operator<<(std::ostream& os, ErrorCode ec) {
  return os << static_cast<int>(ec);
}

int main() {
  fmt::print("{}", ErrorCode::SOME_ERROR);
}

If you don't want to overload operator<<, you can provide a format_arg function instead but it is more evolved and not yet documented:

template <typename ArgFormatter>
void format_arg(fmt::BasicFormatter<char, ArgFormatter> &f,
                const char *&format_str, ErrorCode e) {
  f.writer() << (int)e;
}

There is an example here: https://github.com/fmtlib/fmt/blob/master/fmt/time.h#L18.

The same can be done for std::optional.

Okay got it thanks! Is it possible to template this? e.g.

template<typename T> 
std::ostream &operator<<(std::ostream& os, optional<T> o) {
    if (o) {
        return os << o;
    } else {
        return os << "<nullopt>";
    }
}

When I use this with a stringstream it compiles fine, e.g.:

std::optional<string> test = nullopt;
stringstream ss;
ss << test;

But if I do the same thing with fmt::format it doesn't resolve the template.

It should be possible to do that, but keep in mind that overloaded operator<< should either be declared before it is used in fmt/ostream.h or placed in the std namespace for ADL.

Yes indeed - include order was my issue. Thank you for pointing me in the right direction.

We wanted to have something that "just work" for the whole project so based on the advice in this thread we ended up with this.

#include <ostream>
#include <type_traits>

// clang-format off
// This is required to format enum classes with fmt as they're not implicitly
// convertible to int.
// This operator has to be declared before including format.h and ostream.h.
// We keep clang format disabled to avoid reordering and breaking things
// https://github.com/fmtlib/fmt/issues/391
template<typename T, typename std::enable_if_t<std::is_enum<T>::value, int> = 0>
std::ostream& operator<<(std::ostream& os, const T& value)
{
  return os << static_cast<int>(value);
}

#include <fmt/format.h>
#include <fmt/ostream.h>
// clang-format on

Put this in a header file like enum_format.hpp and include that instead of fmt/format.h and enums should just work everywhere in the project.

In recent versions of {fmt} enum classes are formatted as integers by default (https://godbolt.org/z/3Z9sj4):

#include <fmt/core.h>

enum class ErrorCode : int {
  SOME_ERROR
};

int main() {
  fmt::print("{}", ErrorCode::SOME_ERROR); // prints 0
}
Was this page helpful?
0 / 5 - 0 ratings