The Battle for Wesnoth  1.15.12+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 <cassert>
81 #include <exception>
82 #include <string>
83 
84 #include <boost/preprocessor/cat.hpp>
85 #include <boost/preprocessor/seq/for_each.hpp>
86 #include <boost/preprocessor.hpp>
87 
88 #include <istream>
89 #include <ostream>
90 
91 class bad_enum_cast : public std::exception
92 {
93 public:
94  bad_enum_cast(const std::string& enumname, const std::string& str)
95  : message("Failed to convert string \"" + str + "\" to type " + enumname)
96  , name(enumname)
97  , bad_val(str)
98  {}
99 
100  virtual ~bad_enum_cast() noexcept {}
101 
102  const char * what() const noexcept
103  {
104  return message.c_str();
105  }
106 
107  const char * type() const noexcept
108  {
109  return name.c_str();
110  }
111 
112  const char * value() const noexcept
113  {
114  return bad_val.c_str();
115  }
116 
117 private:
118  std::string message, name, bad_val;
119 };
120 
121 namespace make_enum_detail
122 {
123  void debug_conversion_error(const std::string& tmp, const bad_enum_cast & e);
124 }
125 
126 
127 #define ADD_PAREN_1( A, B ) ((A, B)) ADD_PAREN_2
128 #define ADD_PAREN_2( A, B ) ((A, B)) ADD_PAREN_1
129 #define ADD_PAREN_1_END
130 #define ADD_PAREN_2_END
131 #define MAKEPAIRS( INPUT ) BOOST_PP_CAT(ADD_PAREN_1 INPUT,_END)
132 #define PP_SEQ_FOR_EACH_I_PAIR(macro, data, pairs) BOOST_PP_SEQ_FOR_EACH_I(macro, data, MAKEPAIRS(pairs))
133 
134 
135 #define CAT2( A, B ) A ## B
136 #define CAT3( A, B, C ) A ## B ## C
137 
138 
139 #define EXPAND_ENUMVALUE_NORMAL(r, data, i, record) \
140  BOOST_PP_TUPLE_ELEM(2, 0, record) = i,
141 
142 
143 #define EXPAND_ENUMFUNC_NORMAL(r, data, i, record) \
144  if(data == BOOST_PP_TUPLE_ELEM(2, 1, record)) return BOOST_PP_TUPLE_ELEM(2, 0, record);
145 #define EXPAND_ENUMPARSE_NORMAL(r, data, i, record) \
146  if(data == BOOST_PP_TUPLE_ELEM(2, 1, record)) { *this = BOOST_PP_TUPLE_ELEM(2, 0, record); return true; }
147 #define EXPAND_ENUMFUNCREV_NORMAL(r, data, i, record) \
148  if(data == BOOST_PP_TUPLE_ELEM(2, 0, record)) return BOOST_PP_TUPLE_ELEM(2, 1, record);
149 
150 #define EXPAND_ENUMFUNCTIONCOUNT(r, data, i, record) \
151  + 1
152 
153 class enum_tag
154 {
155 };
156 #define MAKE_ENUM(NAME, CONTENT) \
157 struct NAME : public enum_tag \
158 { \
159  enum type \
160  { \
161  PP_SEQ_FOR_EACH_I_PAIR(EXPAND_ENUMVALUE_NORMAL, ,CONTENT) \
162  }; \
163  type v; \
164  NAME(type v) : v(v) {} \
165  /* We don't want a default constructor but we need one in order to make lexical_cast working */ \
166  NAME() : v() {} \
167  /* operator type() const { return v; } */\
168  static NAME string_to_enum (const std::string& str, NAME def) \
169  { \
170  PP_SEQ_FOR_EACH_I_PAIR(EXPAND_ENUMFUNC_NORMAL, str , CONTENT) \
171  return def; \
172  } \
173  static NAME string_to_enum (const std::string& str) \
174  { \
175  PP_SEQ_FOR_EACH_I_PAIR(EXPAND_ENUMFUNC_NORMAL, str , CONTENT) \
176  throw bad_enum_cast( #NAME , str); \
177  } \
178  /** Sets *this to the value given in str, does nothing if str was not valid enum value. */ \
179  /** @returns true iff @a str was a valid enum value. */ \
180  /** @param TStr a std::string or a string_span (from the wesnothd code). */ \
181  template<typename TStr> \
182  bool parse (const TStr& str) \
183  { \
184  PP_SEQ_FOR_EACH_I_PAIR(EXPAND_ENUMPARSE_NORMAL, str , CONTENT) \
185  return false; \
186  } \
187  /* for const char* parameters we cannot use the template above because it would only compare the pointer. */ \
188  bool parse (const char* str) \
189  { \
190  return parse(std::string(str)); \
191  } \
192  static std::string name() \
193  { \
194  return #NAME; \
195  } \
196  static std::string enum_to_string (NAME val) \
197  { \
198  PP_SEQ_FOR_EACH_I_PAIR(EXPAND_ENUMFUNCREV_NORMAL, val.v , CONTENT) \
199  assert(false && "Corrupted enum found with identifier NAME"); \
200  throw "assertion ignored"; \
201  } \
202  static const char* enum_to_cstring (NAME val) \
203  { \
204  PP_SEQ_FOR_EACH_I_PAIR(EXPAND_ENUMFUNCREV_NORMAL, val.v , CONTENT) \
205  assert(false && "Corrupted enum found with identifier NAME"); \
206  throw "assertion ignored"; \
207  } \
208  std::string to_string () const \
209  { \
210  return enum_to_string(*this); \
211  } \
212  const char* to_cstring () const \
213  { \
214  return enum_to_cstring(*this); \
215  } \
216  friend std::ostream& operator<< (std::ostream & os, NAME val) \
217  { \
218  os << enum_to_string(val); \
219  return os; \
220  } \
221  friend std::ostream& operator<< (std::ostream & os, NAME::type val) \
222  { \
223  return (os << NAME(val)); \
224  } \
225  friend std::istream& operator>> (std::istream & is, NAME& val) \
226  { \
227  std::istream::sentry s(is, true); \
228  if(!s) return is; \
229  std::string temp; \
230  is >> temp; \
231  try { \
232  val = string_to_enum(temp); \
233  } catch (const bad_enum_cast & /*e*/) {\
234  is.setstate(std::ios::failbit); \
235  /*make_enum_detail::debug_conversion_error(temp, e); */\
236  } \
237  return is; \
238  } \
239  friend std::istream& operator>> (std::istream & os, NAME::type& val) \
240  { \
241  return (os >> reinterpret_cast< NAME &>(val)); \
242  } \
243  friend bool operator==(NAME v1, NAME v2) \
244  { \
245  return v1.v == v2.v; \
246  } \
247  friend bool operator==(NAME::type v1, NAME v2) \
248  { \
249  return v1 == v2.v; \
250  } \
251  friend bool operator==(NAME v1, NAME::type v2) \
252  { \
253  return v1.v == v2; \
254  } \
255  friend bool operator!=(NAME v1, NAME v2) \
256  { \
257  return v1.v != v2.v; \
258  } \
259  friend bool operator!=(NAME::type v1, NAME v2) \
260  { \
261  return v1 != v2.v; \
262  } \
263  friend bool operator!=(NAME v1, NAME::type v2) \
264  { \
265  return v1.v != v2; \
266  } \
267  /* operator< is used for by std::map and similar */ \
268  friend bool operator<(NAME v1, NAME v2) \
269  { \
270  return v1.v < v2.v; \
271  } \
272  template<typename T> \
273  T cast() \
274  { \
275  return static_cast<T>(v); \
276  } \
277  static NAME from_int(int i) \
278  { \
279  return static_cast<type>(i); \
280  } \
281  static const std::size_t count = 0 PP_SEQ_FOR_EACH_I_PAIR(EXPAND_ENUMFUNCTIONCOUNT, , CONTENT);\
282  bool valid() \
283  { \
284  return cast<std::size_t>() < count; \
285  } \
286 private: \
287  /*prevent automatic conversion for any other built-in types such as bool, int, etc*/ \
288  /*template<typename T> \
289  operator T () const; */\
290  /* For some unknown reason the following version doesn't compile: */ \
291  /* template<typename T, typename = typename boost::enable_if<Cond>::type> \
292  operator T(); */\
293 };
std::string bad_val
Definition: make_enum.hpp:118
virtual ~bad_enum_cast() noexcept
Definition: make_enum.hpp:100
std::string message
Definition: make_enum.hpp:118
const char * what() const noexcept
Definition: make_enum.hpp:102
const char * value() const noexcept
Definition: make_enum.hpp:112
const char * type() const noexcept
Definition: make_enum.hpp:107
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:118
bad_enum_cast(const std::string &enumname, const std::string &str)
Definition: make_enum.hpp:94