I've recently been introduced to SFINAE to solve the problem of unwanted promotion precedence.
i.e. I was hoping to catch integer types with Foo::Foo(long)
and floating-point types with Foo::Foo(double)
, but alas int
-> double
rather than long
.
/*
Constructors for various types, for example, 'Object{"foo"}, Object{42}, Object{3.14} should create a String Long Float respectively
There is a problem with constructors.
Python supports a single integer type, which we wrap with the Long class
And the single floatingpoint type, which we wrap with the Float class
In an ideal world we would just provide two overloads to allow a generic
Object to be initialised as one of these types:
Object(long l) : Object{ Long {l} } { }
Object(double d) : Object{ Float{d} } { }
We want Object{5} to create a Long{5}
Unfortunately, int gets promoted to double, not long ( http://stackoverflow.com/a/27276398/435129 )
Very annoying. Fortunately we can use some SFINAE cunning.
*/
#define DECAY(T) \
typename std::decay<T>::type
#define IS_INTEGRAL(T) \
std::is_integral< DECAY(T) >::value
#define IS_FLOATING(T) \
std::is_floating_point< DECAY(T) >::value
#define SUBFAIL_UNLESS(PRED) \
typename X = typename std::enable_if<PRED>::type
/*
Note that the first template encounters a substitution failure for any non-integral type,
hence only redirects integral types to init(long(t))
Similarly for floatingpoint types.
Note also the unfortunate use of an empty '...' C parameter expansion.
Without this the compiler will complain that two templates are attempting
to wrap a function with the same signature.
Unfortunately it isn't smart enough to know that the conditions are mutually exclusive.
If you required trapping of three or more such special cases, see NOTE_3_CASES at the bottom of the file.
Also http://ideone.com/oDOSLH
*/
public:
template<typename T, SUBFAIL_UNLESS(IS_INTEGRAL(T)) > explicit Object( T&& t ) { init(long (t)); }
template<typename T, SUBFAIL_UNLESS(IS_FLOATING(T)) > explicit Object( T&& t, ... ) { init(double(t)); }
private:
void init(long);
void init(double);
#undef DECAY
#undef IS_INTEGRAL
#undef IS_FLOATING
I know that macros are generally frowned upon. However, in this case they provide a very clear solution, making it very easy for my brain to parse the code.
I would like to know whether in this case the use of macros is justified, and if not what the best alternative would be.