Note: This is a question-with-answer in order to document a technique that others might find useful, and in order to perhaps become aware of others’ even better solutions. Do feel free to add critique or questions as comments. Also do feel free to add additional answers. :)
rfc/cppx/text/String.h
, I found the following mysterious snippet:
template< class S, class enable = CPPX_IF_( Is_a_< String, S > ) >
void operator! ( S const& )
{ string_detail::Meaningless::operation(); }
The operator!
is in support of a String
class that has an implicit conversion to raw pointer. So I overload (among others) operator!
for this class and derived classes, so that inadvertent use of a non-supported operator will give a suitable compilation error, mentioning that it's meaningless and inaccessible. Which I think is much preferable to such usage being silently accepted with an unexpected, incorrect result.
The CPPX_IF_
macro supports Visual C++ 12.0 (2013) and earlier, which finds C++11 using
to be mostly beyond its ken. For a more standard-conforming compiler I would have written just …
template< class S, class enable = If_< Is_a_< String, S > > >
void operator! ( S const& )
{ string_detail::Meaningless::operation(); }
This looks like std::enable_if
,
template< class S, class enabled = typename std::enable_if< Is_a_< String, S >::value, void >::type >
void operator! ( S const& )
{ string_detail::Meaningless::operation(); }
except that the If_
or CPPX_IF_
, and its expression, is much more concise and readable.
How on Earth did I do that?
In C++14, variable templates make type traits a lot more comfortable to look at. Combine that with C++11 template aliases, and all the cruft disappears:
template <typename A, typename B>
bool is_base_of_v = std::is_base_of<A, B>::value;
template <bool B, typename T = void>
using enable_if_t = typename std::enable_if<B, T>::type;
Usage:
template <typename B, typename D>
enable_if_t<is_base_of_v<B, D>, Foo> some_function(B & b, D & d) { /* ... */ }
"Type" aliases of the form _t
are in fact planned as part of the standard library for C++14, see [meta.type.synop].
The facility for a comfortable-with-C++11-using
compiler is simply …
namespace cppx {
using std::enable_if;
template< class Condition_type, class Result_type = void >
using If_ = typename enable_if<Condition_type::value, Result_type>::type;
} // namespace cppx
Support for a more using
-challenged compiler such as Visual C++ 12.0 and earlier (it understands the basic use of using
but gets more and more unreliable the more the usage context has things like enable_if
) is a bit more involved, building on a C++03-style solution like …
namespace cppx {
using std::enable_if;
template<
class Condition_type,
class Result_type = void,
class enabled = typename enable_if<Condition_type::value, void>::type
>
struct If_T_
{
typedef Result_type T;
typedef Result_type type;
};
} // namespace cppx
This basically only provides a more readable name and dispenses with the ::value
in a condition. In order to also dispense with typename
and ::type
I use a macro. But since the expression will generally be a template expression there may be commas that the preprocessor will interpret as argument separators, so that the preprocessor may see multiple arguments.
The solution I use for that (the time of C++03 is over for me) is to use a C99/C++11 variadic macro, …
#define CPPX_IF_( ... ) \
typename cppx::If_T_< __VA_ARGS__ >::T
A corresponding macro could be defined for use of this functionality without the typename
.
Complete listing, file rfc/cppx/utility/If_.h
:
#pragma once
// Copyright (c) 2013 Alf P. Steinbach
#include <type_traits> // std::enable_if
#define CPPX_IF_( ... ) \
typename cppx::If_T_< __VA_ARGS__ >::T
namespace cppx {
using std::enable_if;
template< class Condition_type, class Result_type = void >
using If_ = typename enable_if<Condition_type::value, Result_type>::type;
template<
class Condition_type,
class Result_type = void,
class enabled = typename enable_if<Condition_type::value, void>::type
>
struct If_T_
{
typedef Result_type T;
typedef Result_type type;
};
} // namespace cppx
Also, for completeness, Is_a_
is defined simply as …
template< class Base, class Derived_or_eq >
using Is_a_ = std::is_base_of<Base, Derived_or_eq>;
which is a use of using
that Visual C++ 12.0 does understand.
To be able to use compound conditions without writing ::value
everywhere, the following definitions come in handy. Essentially these are boolean operators that operate on types. It is perhaps worth noting especially the general exclusive OR operator, which is not implemented in terms of binary XOR (e.g. !=
): that would have yielded an operator that checked for the odd number of true
values, which is of little practical utility except for the special case of exactly two arguments.
namespace cppx {
using std::integral_constant;
template< bool c >
using Bool_ = integral_constant<bool, c>;
using False = Bool_<false>; // std::false_type;
using True = Bool_<true>; // std::true_type;
template< bool v, class First, class... Rest >
struct Count_
{
enum{ value = Count_<v, First>::value + Count_<v, Rest...>::value };
};
template< bool v, class X >
struct Count_<v, X>
{
enum{ value = int(!!X::value == v) };
};
template< class X >
using Not_ = Bool_<Count_<true, X>::value == 0>; // NOT
template< class... Args >
using All_ = Bool_<Count_<false, Args...>::value == 0>; // AND
template< class... Args >
using Some_ = Bool_<Count_<true, Args...>::value != 0>; // General inclusive OR.
template< class... Args >
using Either_ = Bool_<Count_<true, Args...>::value == 1>; // General exclusive OR.
} // namespace cppx
Disclaimer: none of the code has been extensively tested, and C++ compiler quirks in the area of template meta-programming are common.
We have C++03 and C++11 and C++14 solutions, but Concepts Lite is missing:
template <typename Derived, typename Base>
constexpr bool Is_a_() {
return std::is_base_of<Base, Derived>::value;
}
template<Is_a_<String> S>
void operator! ( S const& )
{ string_detail::Meaningless::operation(); }
or the even more terse:
template <typename Derived, typename Base>
concept bool Is_a_() {
return std::is_base_of<Base, Derived>::value;
}
void operator! ( Is_a_<String> const& )
{ string_detail::Meaningless::operation(); }
I highly recommend skimming through the tutorial (Section 2) of the Concepts Lite paper to get a sense of just how much better the world will be after we are freed from our enable_if
overlords.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With