Here is a template class that enables writing and reading enum class members as strings. It is a simplification of Loki Asari's design. It avoids the need for helper functions, as suggested by Veski, by using the enable_if<> and is_enum<> templates.
The idea is to replace template <T> with
template <typename T,
typename std::enable_if<std::is_enum<T>::value>::type* = nullptr>
The consequence is that the operator<<() and operator>>() templates are only instantiated for enums (because of the Substitution Failure Is Not An Error (SFINAE) principle). Then the helper classes enumConstRefHolder and enumRefHolder, and the functions enumToString() and enumFromString(), are no longer needed.
In addition to recognizing an enum member by a string (normalized to be all capital letters), the code recognizes its integer representation. (For the example below, both "FaST" and "1" will be read as Family::FAST.)
EnumIO.h:
#ifndef _EnumIO_h
#define _EnumIO_h
#include <ios>
#include <iostream>
#include <sstream>
#include <vector>
// A template class that enables writing and reading enum class
// members as strings.
//
// Author: Bradley Plohr (2017-05-12)
//
// Note: The idea to keep the enum names as a static member in a
// template comes from Loki Asari:
//
// https://codereview.stackexchange.com/questions/14309
// /conversion-between-enum-and-string-in-c-class-header
//
// Usage example:
//
// Enums.h:
// -------
// #ifndef _Enums_h
// #define _Enums_h
//
// enum class Family { SLOW, FAST };
//
// TODO: other enum classes
//
// #endif /* Enums_h */
//
//
// Enums.cc:
// --------
// #include "EnumIO.h"
// #include "Enums.h"
//
// template<>
// std::vector<std::string>
// EnumIO<Family>::enum_names({ "SLOW", "FAST" });
//
// TODO: enum names for other enum classes
//
//
// t_enum.cc:
// ---------
// #include <iostream>
// #include "EnumIO.h"
// #include "Enums.h"
//
// int
// main()
// {
// Family family;
//
// family = Family::SLOW;
// std::cout << family << std::endl;
//
// std::cin >> family;
// std::cout << family << std::endl;
//
// return 0;
// }
template <typename T>
class EnumIO
{
public:
static std::string name;
static std::vector<std::string> enum_names;
};
template <typename T,
typename std::enable_if<std::is_enum<T>::value>::type* = nullptr>
std::ostream&
operator<<(std::ostream& os, const T& t)
{
os << EnumIO<T>::enum_names[static_cast<int>(t)];
return os;
}
template <typename T,
typename std::enable_if<std::is_enum<T>::value>::type* = nullptr>
std::istream&
operator>>(std::istream& is, T& t)
{
std::string input;
is >> input;
if (is.fail())
return is;
for (auto& c : input)
c = std::toupper(c);
int n = static_cast<int>(EnumIO<T>::enum_names.size());
// check for a match with a name
for (int i = 0; i < n; ++i)
if (input == EnumIO<T>::enum_names[i]) {
// Here we assume that the integer representation of
// the enum class is the default. If the enum class
// members are assigned other integers, this code
// must be extended by consulting a vector containing
// the assigned integers.
t = static_cast<T>(i);
return is;
}
// check for a match with an integer
std::istringstream iss(input);
int value;
iss >> value;
if (!iss.fail())
for (int i = 0; i < n; ++i)
if (value == i) {
// See the comment above.
t = static_cast<T>(i);
return is;
}
is.setstate(std::ios::failbit);
return is;
}
#endif /* _EnumIO_h */