The Battle for Wesnoth  1.17.0-dev
make_enum.hpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2021
3  by Chris Beck <render787@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Defines the MAKE_ENUM macro.
19  *
20  * Currently this comes in 1-argument and 2-argument versions.
21  *
22  * <b>Example usage:</b>
23  *
24  * @code
25  * #include "utils/make_enum.hpp"
26  *
27  * MAKE_ENUM(enumname,
28  * (val1, "name1")
29  * (val2, "name2")
30  * (val3, "name3")
31  * )
32  * @endcode
33  *
34  * <b>What it does:</b>
35  *
36  * Generates a struct @a enumname which holds an enum and provides functions to
37  * convert to and from string, as well as a constant @a enumname::count, which
38  * is set to the number of possible enum values defined.
39  *
40  * @code
41  * // Throws bad_enum_cast if the string value is not recognized.
42  * enumname enumname::string_to_enum(const std::string&);
43  *
44  * // Returns the specified default instead of throwing if the string value is
45  * // not recognized. Never throws.
46  * enumname enumname::string_to_enum(const std::string&, enumname);
47  *
48  * // Never throws.
49  * std::string enumname::enum_to_string(enumname);
50  *
51  * // Count of defined enum values.
52  * const std::size_t enumname::count;
53  * @endcode
54  *
55  * It also defines the following stream operations:
56  *
57  * @code
58  * // Never throws. Fails an assertion check if the value passed is not defined
59  * // by the enumeration.
60  * std::ostream& operator<<(std::ostream&, enumname);
61  *
62  * // Never throws. Sets the stream's fail bit if the input is not recognized.
63  * std::istream& operator>>(std::istream&, enumname&);
64  * @endcode
65  *
66  * In case of a bad string -> enum conversion from istream input, the istream
67  * will have its fail bit set. This means that lexical_casts will throw a
68  * bad_lexical_cast in the similar scenario; however, note that
69  * bad_lexical_cast does not provide any details about the error.
70  *
71  * It is recommended to use MAKE_ENUM types with the built-in versions of
72  * lexical_cast or lexical_cast_default provided by Wesnoth (see lexical_cast.hpp).
73  * However, if you do <b>not</b> want wml_exception to be thrown under any
74  * circumstances, use the string_to_enumname functions instead.
75  *
76  * See src/tests/test_make_enum.cpp for example code.
77  */
78 
79 #pragma once
80 
81 #include <cassert>
82 #include <exception>
83 #include <string>
84 
85 #include <boost/preprocessor/cat.hpp>
86 #include <boost/preprocessor/seq/for_each.hpp>
87 #include <boost/preprocessor.hpp>
88 
89 #include <istream>
90 #include <ostream>
91 
92 class bad_enum_cast : public std::exception
93 {
94 public:
95  bad_enum_cast(const std::string& enumname, const std::string& str)
96  : message("Failed to convert string \"" + str + "\" to type " + enumname)
97  , name(enumname)
98  , bad_val(str)
99  {}
100 
101  virtual ~bad_enum_cast() noexcept {}
102 
103  const char * what() const noexcept
104  {
105  return message.c_str();
106  }
107 
108  const char * type() const noexcept
109  {
110  return name.c_str();
111  }
112 
113  const char * value() const noexcept
114  {
115  return bad_val.c_str();
116  }
117 
118 private:
119  std::string message, name, bad_val;
120 };
121 
122 namespace make_enum_detail
123 {
124  void debug_conversion_error(const std::string& tmp, const bad_enum_cast & e);
125 }
126 
127 
128 #define ADD_PAREN_1( A, B ) ((A, B)) ADD_PAREN_2
129 #define ADD_PAREN_2( A, B ) ((A, B)) ADD_PAREN_1
130 #define ADD_PAREN_1_END
131 #define ADD_PAREN_2_END
132 #define MAKEPAIRS( INPUT ) BOOST_PP_CAT(ADD_PAREN_1 INPUT,_END)
133 #define PP_SEQ_FOR_EACH_I_PAIR(macro, data, pairs) BOOST_PP_SEQ_FOR_EACH_I(macro, data, MAKEPAIRS(pairs))
134 
135 
136 #define CAT2( A, B ) A ## B
137 #define CAT3( A, B, C ) A ## B ## C
138 
139 
140 #define EXPAND_ENUMVALUE_NORMAL(r, data, i, record) \
141  BOOST_PP_TUPLE_ELEM(2, 0, record) = i,
142 
143 
144 #define EXPAND_ENUMFUNC_NORMAL(r, data, i, record) \
145  if(data == BOOST_PP_TUPLE_ELEM(2, 1, record)) return BOOST_PP_TUPLE_ELEM(2, 0, record);
146 #define EXPAND_ENUMPARSE_NORMAL(r, data, i, record) \
147  if(data == BOOST_PP_TUPLE_ELEM(2, 1, record)) { *this = BOOST_PP_TUPLE_ELEM(2, 0, record); return true; }
148 #define EXPAND_ENUMFUNCREV_NORMAL(r, data, i, record) \
149  if(data == BOOST_PP_TUPLE_ELEM(2, 0, record)) return BOOST_PP_TUPLE_ELEM(2, 1, record);
150 
151 #define EXPAND_ENUMFUNCTIONCOUNT(r, data, i, record) \
152  + 1
153 
154 class enum_tag
155 {
156 };
157 #define MAKE_ENUM(NAME, CONTENT) \
158 struct NAME : public enum_tag \
159 { \
160  enum type \
161  { \
162  PP_SEQ_FOR_EACH_I_PAIR(EXPAND_ENUMVALUE_NORMAL, ,CONTENT) \
163  }; \
164  type v; \
165  NAME(type v) : v(v) {} \
166  /* We don't want a default constructor but we need one in order to make lexical_cast working */ \
167  NAME() : v() {} \
168  /* operator type() const { return v; } */\
169  static NAME string_to_enum (const std::string& str, NAME def) \
170  { \
171  PP_SEQ_FOR_EACH_I_PAIR(EXPAND_ENUMFUNC_NORMAL, str , CONTENT) \
172  return def; \
173  } \
174  static NAME string_to_enum (const std::string& str) \
175  { \
176  PP_SEQ_FOR_EACH_I_PAIR(EXPAND_ENUMFUNC_NORMAL, str , CONTENT) \
177  throw bad_enum_cast( #NAME , str); \
178  } \
179  /** Sets *this to the value given in str, does nothing if str was not valid enum value. */ \
180  /** @returns true iff @a str was a valid enum value. */ \
181  /** @param TStr a std::string or a string_span (from the wesnothd code). */ \
182  template<typename TStr> \
183  bool parse (const TStr& str) \
184  { \
185  PP_SEQ_FOR_EACH_I_PAIR(EXPAND_ENUMPARSE_NORMAL, str , CONTENT) \
186  return false; \
187  } \
188  /* for const char* parameters we cannot use the template above because it would only compare the pointer. */ \
189  bool parse (const char* str) \
190  { \
191  return parse(std::string(str)); \
192  } \
193  static std::string name() \
194  { \
195  return #NAME; \
196  } \
197  static std::string enum_to_string (NAME val) \
198  { \
199  PP_SEQ_FOR_EACH_I_PAIR(EXPAND_ENUMFUNCREV_NORMAL, val.v , CONTENT) \
200  assert(false && "Corrupted enum found with identifier NAME"); \
201  throw "assertion ignored"; \
202  } \
203  static const char* enum_to_cstring (NAME val) \
204  { \
205  PP_SEQ_FOR_EACH_I_PAIR(EXPAND_ENUMFUNCREV_NORMAL, val.v , CONTENT) \
206  assert(false && "Corrupted enum found with identifier NAME"); \
207  throw "assertion ignored"; \
208  } \
209  std::string to_string () const \
210  { \
211  return enum_to_string(*this); \
212  } \
213  const char* to_cstring () const \
214  { \
215  return enum_to_cstring(*this); \
216  } \
217  friend std::ostream& operator<< (std::ostream & os, NAME val) \
218  { \
219  os << enum_to_string(val); \
220  return os; \
221  } \
222  friend std::ostream& operator<< (std::ostream & os, NAME::type val) \
223  { \
224  return (os << NAME(val)); \
225  } \
226  friend std::istream& operator>> (std::istream & is, NAME& val) \
227  { \
228  std::istream::sentry s(is, true); \
229  if(!s) return is; \
230  std::string temp; \
231  is >> temp; \
232  try { \
233  val = string_to_enum(temp); \
234  } catch (const bad_enum_cast & /*e*/) {\
235  is.setstate(std::ios::failbit); \
236  /*make_enum_detail::debug_conversion_error(temp, e); */\
237  } \
238  return is; \
239  } \
240  friend std::istream& operator>> (std::istream & os, NAME::type& val) \
241  { \
242  return (os >> reinterpret_cast< NAME &>(val)); \
243  } \
244  friend bool operator==(NAME v1, NAME v2) \
245  { \
246  return v1.v == v2.v; \
247  } \
248  friend bool operator==(NAME::type v1, NAME v2) \
249  { \
250  return v1 == v2.v; \
251  } \
252  friend bool operator==(NAME v1, NAME::type v2) \
253  { \
254  return v1.v == v2; \
255  } \
256  friend bool operator!=(NAME v1, NAME v2) \
257  { \
258  return v1.v != v2.v; \
259  } \
260  friend bool operator!=(NAME::type v1, NAME v2) \
261  { \
262  return v1 != v2.v; \
263  } \
264  friend bool operator!=(NAME v1, NAME::type v2) \
265  { \
266  return v1.v != v2; \
267  } \
268  /* operator< is used for by std::map and similar */ \
269  friend bool operator<(NAME v1, NAME v2) \
270  { \
271  return v1.v < v2.v; \
272  } \
273  template<typename T> \
274  T cast() \
275  { \
276  return static_cast<T>(v); \
277  } \
278  static NAME from_int(int i) \
279  { \
280  return static_cast<type>(i); \
281  } \
282  static const std::size_t count = 0 PP_SEQ_FOR_EACH_I_PAIR(EXPAND_ENUMFUNCTIONCOUNT, , CONTENT);\
283  bool valid() \
284  { \
285  return cast<std::size_t>() < count; \
286  } \
287 private: \
288  /*prevent automatic conversion for any other built-in types such as bool, int, etc*/ \
289  /*template<typename T> \
290  operator T () const; */\
291  /* For some unknown reason the following version doesn't compile: */ \
292  /* template<typename T, typename = typename boost::enable_if<Cond>::type> \
293  operator T(); */\
294 };
std::string bad_val
Definition: make_enum.hpp:119
virtual ~bad_enum_cast() noexcept
Definition: make_enum.hpp:101
std::string message
Definition: make_enum.hpp:119
const char * what() const noexcept
Definition: make_enum.hpp:103
const char * value() const noexcept
Definition: make_enum.hpp:113
const char * type() const noexcept
Definition: make_enum.hpp:108
void debug_conversion_error(const std::string &temp, const bad_enum_cast &e)
Definition: make_enum.cpp:22
#define e
std::string name
Definition: make_enum.hpp:119
bad_enum_cast(const std::string &enumname, const std::string &str)
Definition: make_enum.hpp:95