Here is a template class that enables writing and reading enum class members as strings. It is a simplification of Loki Astari'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 <algorithm>
#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 Astari:
//
// 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 "Enums.h"
// #include "EnumIO.h"
// #include <string>
// #include <vector>
//
// template <>
// const std::vector<std::string>& EnumIO<Family>::enum_names()
// {
// static std::vector<std::string> enum_names_({ "SLOW", "FAST" });
// return enum_names_;
// }
//
// TODO: enum names for other enum classes
//
//
// t_EnumIO.cc:
// -----------
// #include "EnumIO.h"
// #include "Enums.h"
// #include <iostream>
//
// int
// main()
// {
// Family family;
//
// family = Family::SLOW;
// std::cout << family << std::endl;
//
// std::cin >> family;
// std::cout << family << std::endl;
//
// return 0;
// }
//
// For the input
//
// fAsT
//
// the output is
//
// SLOW
// FAST
template <typename T>
class EnumIO
{
public:
static const 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().at(static_cast<int>(t));
return os;
}
static std::string
toUpper(const std::string& input)
{
std::string copy(input);
std::transform(copy.cbegin(), copy.cend(), copy.begin(),
[](const unsigned char i) { return std::toupper(i); });
return copy;
}
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;
input = toUpper(input);
// check for a match with a name
int i = 0;
for (auto name : EnumIO<T>::enum_names()) {
if (toUpper(name) == input) {
// 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;
}
++i;
}
// check for a match with an integer
int n = static_cast<int>(EnumIO<T>::enum_names().size());
std::istringstream iss(input);
int value;
iss >> value;
if (not iss.fail() && 0 <= value && value < n) {
t = static_cast<T>(value); // See the comment above.
return is;
}
is.setstate(std::ios::failbit);
return is;
}
#endif /* ENUMIO_H_ */