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