I'm still unsure of whether it is a good idea to ask this, as it's somehow a "coding style" problem (and those are inherently subjective), but I hope it will be objective enough turned that way.
If it turns out it isn't, I apologize for asking it here.
I'd like to ask two questions:
- is there important disadvantages of using that macro compared to copy-pasting the actual code I could have disregarded? (or any better solution you could think of)
- is there a better solution to achieve the same goal? (avoiding repeating the "boilerplate metaprogramming code")
What I don't ask however is whether you personally like this code or not. I can't prevent you from saying so if you want, and I won't mind if you do, but I'd like to have a reason why it's either good or bad and it may help leading this question to the dark side of potentially opinion-based questions (assuming it isn't already, again, I apologize if it is).
Recently, I wanted to write some traits many of them being extremely simple. However, each of those required some kind of repetitive metaprogramming code. I ended up using a macro to make the code more compact and less repetitive with a code like this:
#include <type_traits>
//...
template<class...>
struct void_type {
typedef void type;
};
template<class... T>
using void_t = void_type<T...>::type;
#define UNARY_EXPRESSION_TYPE_TRAIT(trait_name, expression, expression_type, \
type_name) \
namespace detail_generated { \
template<class type_name, class = void> \
struct trait_name : std::false_type {}; \
\
template<class type_name> \
struct trait_name<type_name, void_t<decltype(expression)>> : \
std::integral_constant< \
bool, std::is_same<decltype(expression), expression_type>::value \
> {}; \
} \
template<class type_name> \
struct trait_name<type_name> : detail_generated::trait_name<type_name> {};
UNARY_EXPRESSION_TYPE_TRAIT(is_preincrementable,
++std::declval<Incrementable&>(),
Incrementable&, Incrementable)
UNARY_EXPRESSION_TYPE_TRAIT(is_predecrementable,
--std::declval<Decrementable&>(),
Decrementable&, Decrementable)
UNARY_EXPRESSION_TYPE_TRAIT(is_contextually_convertible_to_bool,
!std::declval<T>(), bool, T)
#undef UNARY_EXPRESSION_TYPE_TRAIT
// ...
In this specific case it avoids repetition of "boilerplate metaprogramming constructions" and I don't see that many drawbacks (most of them being mitigated by the immediate use of the macro, and its immediate undefinition).
However, I'm usually not really comfortable around macros (I find them obfuscated, and usually replaceable) and I know they are usually regarded as bad or "evil". Unfortunately I couldn't find either a way to replace it, a reason to keep it, or a reason to avoid it, even after reading the following questions:
- When are C++ macros beneficial?
- Why #define is bad?
- What is an appropriate use scenario of #define in C++?
(and that one because it's fun although not really useful:)
I am not satisfied with their answers because they either:
- only recommend macros in case of include guards without explaining why macros are always a bad idea in all other cases (i assume they discourage the use of macros because it is extremely easy to misuse them)
- also recommend them for debug functions (which is not the case here)
- say macros are great in some cases then post an example i actually find quite dissuasive
- claim that macros are a sign of bad design (that might be true, but then i'd like to know where the problem is so that I could pick a better solution)
I can't objectively judge my code, and I can't find any good replacement for that macro (except actually doing the preprocessor's job and copy-pasting that macro wherever I need which strikes me as at least equally bad). Maybe I just don't see the obvious and fatal disadvantages of that macro, or maybe I even didn't fully understand why using macros is discouraged.
Edit: removed the leading underscore in some names as recommended in the comments (such names are reserved)
__
(double underscore), anything starting with_
and an uppercase letter is reserved anywhere. Anything starting with_
is reserved in the global namespace. – ex-bart