patterncppMinor
Enum to string with template metaprogramming
Viewed 0 times
templatewithenummetaprogrammingstring
Problem
This allows enum values to be outputted as strings. But
Note: This compiles on GCC 5.3. I did not run it on other compilers.
```
#include
#include
#include
#include
#include
template
struct string_literal {
static constexpr char value[sizeof...(Cs)] = {Cs...};
};
template constexpr char string_literal::value[sizeof...(Cs)];
template
constexpr string_literal operator ""_e() { return {}; }
enum Colour {Red, Blue, Green};
enum Animal {Dog, Cat, Bird};
enum Fruit {Apple = -1, Orange = 3, Grape = 5, Banana = 100};
template
struct E : std::integral_constant {
using type = T;
};
using enum_strings = std::tuple, E, E>,
std::tuple, E, E>,
std::tuple, E, E, E>
>;
template struct get_enum_pack;
template class P, typename First, typename... Rest>
struct get_enum_pack> : get_enum_pack> {};
template class P, typename... Es, typename... Packs>
struct get_enum_pack, Packs...>> {
using type = P;
};
template struct enum_binary_search;
template class P, int... Is, typename... Ts>
struct enum_binary_search...>> {
using Tuple = std::tuple...>;
static std::istream& execute (std::istream& is, T& t) {
std::string buf;
is >> buf;
execute_impl(buf, t);
return is;
}
private:
template
static void execute_impl (const std::string& buf, T& t) {
constexpr std::size_t MidIndex = (MinIndex + MaxIndex) / 2;
using S = std::tuple_element_t;
const std::string str = S::type::value;
if (buf == str)
t = static_cast(S::value);
else if (buf (buf, t); // Replacing MidIndex with MidIndex - 1 leads to compiling error (array subscript out of bounds) for reasons I don't understand.
else if
enum_strings is a type, not a map or a vector, so if the enum value is known during compile time, there will be no look-up time at all. If the enum value is a run-time value, then the look-up time is \$O(\log N)\$ due to a binary search being made through the pack. Note: This compiles on GCC 5.3. I did not run it on other compilers.
```
#include
#include
#include
#include
#include
template
struct string_literal {
static constexpr char value[sizeof...(Cs)] = {Cs...};
};
template constexpr char string_literal::value[sizeof...(Cs)];
template
constexpr string_literal operator ""_e() { return {}; }
enum Colour {Red, Blue, Green};
enum Animal {Dog, Cat, Bird};
enum Fruit {Apple = -1, Orange = 3, Grape = 5, Banana = 100};
template
struct E : std::integral_constant {
using type = T;
};
using enum_strings = std::tuple, E, E>,
std::tuple, E, E>,
std::tuple, E, E, E>
>;
template struct get_enum_pack;
template class P, typename First, typename... Rest>
struct get_enum_pack> : get_enum_pack> {};
template class P, typename... Es, typename... Packs>
struct get_enum_pack, Packs...>> {
using type = P;
};
template struct enum_binary_search;
template class P, int... Is, typename... Ts>
struct enum_binary_search...>> {
using Tuple = std::tuple...>;
static std::istream& execute (std::istream& is, T& t) {
std::string buf;
is >> buf;
execute_impl(buf, t);
return is;
}
private:
template
static void execute_impl (const std::string& buf, T& t) {
constexpr std::size_t MidIndex = (MinIndex + MaxIndex) / 2;
using S = std::tuple_element_t;
const std::string str = S::type::value;
if (buf == str)
t = static_cast(S::value);
else if (buf (buf, t); // Replacing MidIndex with MidIndex - 1 leads to compiling error (array subscript out of bounds) for reasons I don't understand.
else if
Solution
Putting multiple enum values into one
Having a separate
It's complicated and unreadable! This isn't your fault, it's just how template metaprogramming is in C++. Generally,
For example, here's a version that does a linear search, based on this stackoverflow answer.
Similarly, in C++20, a whole bunch of
It should be fairly simple to add binary lookup to
enum_strings data structure means this one piece of code will depend on every single enum definition in the code-base where conversion to a string is required. It's also easy to forget to add a value.Having a separate
enum_strings-type structure for each enum would mean that the strings can be defined near the enum, and one is less likely to forget to add things to one central point. Headers containing unrelated enums from other places also won't be included accidentally.enum_binary_search::execute_impl converts to std::string to do comparisons. This is likely to be very slow (it allocates memory and copys over all the data in the array). It also does the comparison multiple times.std::strcmp can be used to do the comparison once, with no copying.It's complicated and unreadable! This isn't your fault, it's just how template metaprogramming is in C++. Generally,
constexpr-based solutions are simpler, and promise to get much easier with future versions of the language.For example, here's a version that does a linear search, based on this stackoverflow answer.
#include
class CExprStr // use std::string_view in C++17
{
public:
template
constexpr CExprStr(char const (&s)[N]): m_size(N), m_data(s) { }
constexpr char operator[](std::size_t index) const
{
return
(index >= m_size) ? throw std::out_of_range("Invalid index.") :
m_data[index];
}
constexpr char const* data() const
{
return m_data;
}
private:
std::size_t m_size;
char const* m_data;
};
namespace
{
constexpr bool EqualsImpl(CExprStr const& a, char const* b, std::size_t i)
{
return
(a[i] != b[i]) ? false :
(a[i] == 0 || b[i] == 0) ? true :
EqualsImpl(a, b, i + 1);
}
} // unnamed
constexpr bool operator==(CExprStr const& a, char const* b)
{
return EqualsImpl(a, b, 0);
}
#include
#include
template
using EnumStringT = std::tuple;
template
using EnumMapT = std::array, N>;
template
constexpr char const* EnumGetValue(EnumMapT const& map, EnumT key, std::size_t i = 0)
{
return
(i == map.size()) ? throw std::invalid_argument("Enum key not present in map.") : // (will also fail to compile, as it's not a constant expression).
(std::get(map[i]) == key) ? std::get(map[i]).data() :
EnumGetValue(map, key, i + 1);
}
template
constexpr EnumT EnumGetKey(EnumMapT const& map, char const* value, std::size_t i = 0)
{
return
(i == map.size()) ? throw std::invalid_argument("Enum value not present in map.") : // (will also fail to compile, as it's not a constant expression).
(std::get(map[i]) == value) ? std::get(map[i]) :
EnumGetKey(map, value, i + 1);
}
#include
#include
#include
enum class Color { Red, Green, Blue };
namespace
{
using E = EnumStringT;
constexpr EnumMapT ColorStringMap =
{{
E{ Color::Red, "red" },
E{ Color::Green, "green" },
E{ Color::Blue, "blue" },
}};
} // unnamed
constexpr char const* ColorToString(Color value)
{
return EnumGetValue(ColorStringMap, value);
}
std::ostream& operator>(std::istream& stream, Color& value)
{
auto buffer = std::string();
stream >> buffer;
value = StringToColor(buffer.c_str());
return stream;
}
#include
#include
int main()
{
{
constexpr auto red = ColorToString(Color::Red);
//constexpr auto invalid = ColorToString((Color)-1); // won't compile
//auto invalid = ColorToString((Color)-1); // throws std::invalid_argument
std::cout > c;
std::cout << c << std::endl;
}
}CExprStr exists purely to do string comparison between const char*s, since std::strcmp isn't a constexpr function. In C++17, we can delete the whole class and use std::string_view instead.Similarly, in C++20, a whole bunch of
std algorithms become constexpr, including std::find_if, std::binary_search, std::lexicographical_compare, which makes a constexpr based solution more appealing.It should be fairly simple to add binary lookup to
EnumGetValue and EnumGetKey above. Sorting the array in a constexpr function will still be tricky in C++14, but slightly easier in C++17.Code Snippets
#include <stdexcept>
class CExprStr // use std::string_view in C++17
{
public:
template<std::size_t N>
constexpr CExprStr(char const (&s)[N]): m_size(N), m_data(s) { }
constexpr char operator[](std::size_t index) const
{
return
(index >= m_size) ? throw std::out_of_range("Invalid index.") :
m_data[index];
}
constexpr char const* data() const
{
return m_data;
}
private:
std::size_t m_size;
char const* m_data;
};
namespace
{
constexpr bool EqualsImpl(CExprStr const& a, char const* b, std::size_t i)
{
return
(a[i] != b[i]) ? false :
(a[i] == 0 || b[i] == 0) ? true :
EqualsImpl(a, b, i + 1);
}
} // unnamed
constexpr bool operator==(CExprStr const& a, char const* b)
{
return EqualsImpl(a, b, 0);
}
#include <tuple>
#include <stdexcept>
template<class EnumT>
using EnumStringT = std::tuple<EnumT, CExprStr>;
template<class EnumT, std::size_t N>
using EnumMapT = std::array<EnumStringT<EnumT>, N>;
template<class EnumT, std::size_t N>
constexpr char const* EnumGetValue(EnumMapT<EnumT, N> const& map, EnumT key, std::size_t i = 0)
{
return
(i == map.size()) ? throw std::invalid_argument("Enum key not present in map.") : // (will also fail to compile, as it's not a constant expression).
(std::get<0>(map[i]) == key) ? std::get<1>(map[i]).data() :
EnumGetValue(map, key, i + 1);
}
template<class EnumT, std::size_t N>
constexpr EnumT EnumGetKey(EnumMapT<EnumT, N> const& map, char const* value, std::size_t i = 0)
{
return
(i == map.size()) ? throw std::invalid_argument("Enum value not present in map.") : // (will also fail to compile, as it's not a constant expression).
(std::get<1>(map[i]) == value) ? std::get<0>(map[i]) :
EnumGetKey(map, value, i + 1);
}
#include <array>
#include <iostream>
#include <string>
enum class Color { Red, Green, Blue };
namespace
{
using E = EnumStringT<Color>;
constexpr EnumMapT<Color, 3> ColorStringMap =
{{
E{ Color::Red, "red" },
E{ Color::Green, "green" },
E{ Color::Blue, "blue" },
}};
} // unnamed
constexpr char const* ColorToString(Color value)
{
return EnumGetValue(ColorStringMap, value);
}
std::ostream& operator<<(std::ostream& stream, Color value)
{
stream << ColorToString(value);
return stream;
}
constexpr Color StringToColor(char const* value)
{
return EnumGetKey(ColorStringMap, value);
}
std::istream& operator>>(std::istream& stream, Color& value)
{
auto buffer = std::string();
stream >> buffer;
value = StringToColor(buffer.c_str());
return stream;
}
#include <iostream>
#include <sstream>
int main()
{
{
constexpr auto red = ColorToString(Color::Red);
//constexpr auto invalid = ColorToString((Color)-1); // won't compile
//auto invalid = ColorToString((Color)-1); // throws std::invalid_argument
sContext
StackExchange Code Review Q#125953, answer score: 2
Revisions (0)
No revisions yet.