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’
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.
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.