Skip to content

Instantly share code, notes, and snippets.

@pyrtsa
Created April 1, 2012 13:27
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pyrtsa/2275320 to your computer and use it in GitHub Desktop.
Save pyrtsa/2275320 to your computer and use it in GitHub Desktop.
C++11 metaprogramming
// Here are a few tricks I've used with the trunk versions of clang and libc++
// with C++11 compilation turned on. Some might be obvious, some not, but at
// least they are some kind of improvement over their C++03 counterparts.
//
// Public domain.
// =============================================================================
// 1) Using variadic class templates recursively, like in the definitions for
// "add<T...>" here:
#include <type_traits>
// A few convenience aliases first
template <typename T, T N> using ic = std::integral_constant<T, N>;
template <int N> using int_ = std::integral_constant<int, N>;
// Sum any number of integral constants:
template <typename... Args> struct add;
template <>
struct add<>
: ic<int, 0> {};
template <typename A>
struct add<A>
: ic<decltype(+A::value), A::value> {};
template <typename A, typename B, typename... More>
struct add<A, B, More...>
: add<ic<decltype(A::value + B::value),
A::value + B::value>, More...> {};
// --- example -------------------------------------------------------------
add<>::value; // 0
add<int_<1>>::value; // 1
add<int_<1>, int_<2>>::value; // 3
add<int_<1>, int_<2>, int_<3>>::value; // 6
add<int_<1>, int_<2>, int_<3>, int_<4>>::value; // 10
// etc.
// =============================================================================
// 2a) With decltype, the "sizeof(yes_type)" trick is no longer needed for
// implementing traits. This one tests whether there is a type T::type
// defined:
using std::true_type;
using std::false_type;
namespace detail {
template <typename T, typename Type=typename T::type>
struct has_type_helper;
template <typename T> true_type has_type_test(has_type_helper<T> *);
template <typename T> false_type has_type_test(...);
}
template <typename T>
struct has_type : decltype(detail::has_type_test<T>(nullptr)) {};
// --- example -------------------------------------------------------------
has_type<int>::value; // false
has_type<std::is_integral<int>>::value; // true, said type is "bool"
has_type<std::integral_constant<int,1>>::value; // true, said type is "int"
// -----------------------------------------------------------------------------
// 2b) This trait tests whether T is an integral constant:
namespace detail {
template <typename T, decltype(T::value)>
struct integral_constant_helper;
template <typename T> true_type integral_constant_test(
integral_constant_helper<T,T::value> *);
template <typename T> false_type integral_constant_test(...);
}
template <typename T>
struct is_integral_constant
: decltype(detail::integral_constant_test<T>(nullptr)) {};
// --- example -------------------------------------------------------------
is_integral_constant<int>::value; // false
is_integral_constant<std::is_integral<int>>::value; // true
is_integral_constant<std::integral_constant<int,1>>::value; // true
// =============================================================================
// 3) Selection of the first matching type from a list of cases (or "pattern
// matching", if you will):
template <typename... When> struct match;
template <> struct match<> { static constexpr bool value = false; };
template <typename When, typename... More> struct match<When, More...>
: std::conditional<When::value, When, match<More...>>::type {};
// 'match' is meant to be used together with 'when', 'otherwise' and
// friends:
template <bool Cond, typename Then=void> struct when_c;
template <typename Then> struct when_c<true, Then> {
typedef Then type;
static constexpr bool value = true;
};
template <typename Then> struct when_c<false, Then> {
static constexpr bool value = false;
};
template <bool Cond, typename Then=void>
struct when_not_c : when_c<!Cond, Then> {};
template <typename Cond, typename Then=void>
struct when : when_c<Cond::value, Then> {};
template <typename Cond, typename Then=void>
struct when_not : when_not_c<Cond::value, Then> {};
template <typename Then> struct otherwise {
typedef Then type;
static constexpr bool value = true;
};
// --- example -------------------------------------------------------------
struct fizz {};
struct buzz {};
struct fizzbuzz {};
template <int N> struct game : match<
when_c<N % 3 == 0 && N & 5 == 0, fizzbuzz>,
when_c<N % 3 == 0, fizz>,
when_c<N % 5 == 0, buzz>,
otherwise< int_c<N>>
> {};
game<1>::type; // int_<1>
game<2>::type; // int_<2>
game<3>::type; // fizz
game<4>::type; // int_<4>
game<5>::type; // buzz
game<6>::type; // fizz
game<7>::type; // int_<7>
game<8>::type; // int_<8>
game<9>::type; // fizz
game<10>::type; // buzz
game<11>::type; // int_<11>
game<12>::type; // fizz
game<13>::type; // int_<13>
game<14>::type; // int_<14>
game<15>::type; // fizzbuzz
game<16>::type; // int_<16>
// =============================================================================
// 4a) Variadic template template parameters. For instance,
// boost::mpl::quoteN<...> can be reimplemented with just:
template <template <typename...> class F> struct quote {
template <typename... Args> struct apply : F<Args...> {};
};
// -----------------------------------------------------------------------------
// 4b) Here's another use for variadic template template parameters. Of course,
// the standard library offers std::tuple_size<T> for getting the number of
// elements in a tuple. But that metafunction cannot be used for any other
// tuple-like class. Suppose we defined boost::mpl::vector like:
template <typename... T> struct vector {};
// By using a variadic template template, we can define a metafunction which
// works equally for both std::tuple<T...> as well as vector<T...>:
template <typename T> struct size {}; // (no size defined by default)
template <template <typename...> class C, typename... T>
struct size<C<T...>> : ic<std::size_t, sizeof...(T)> {};
template <typename T> struct size<T &> : size<T> {};
template <typename T> struct size<T &&> : size<T> {};
template <typename T> struct size<T const> : size<T> {};
template <typename T> struct size<T volatile> : size<T> {};
template <typename T> struct size<T const volatile> : size<T> {};
// --- example -------------------------------------------------------------
size<tuple<int, int> &>::value; // 2
size<vector<int, int, int>>::value; // 3
size<vector<> const &>::value; // 0
// =============================================================================
// 5) Using nested variadic templates to get many template parameter packs to
// play with:
namespace detail {
template <typename A> struct con;
template <typename... T> struct con<vector<T...>> {
template <typename B> struct cat;
template <typename... U> struct cat<vector<U...>> {
typedef vector<T..., U...> type;
};
};
}
template <typename A, typename B>
struct concat : detail::con<A>::template cat<B> {};
// --- example -------------------------------------------------------------
struct a; struct b; struct c; struct d; struct e;
concat<vector<a, b>, vector<c, d, e>>::type; // vector<a, b, c, d, e>
// =============================================================================
// 6) Defining function result and result type at once.
#define RETURNS(...) decltype((__VA_ARGS__)) { return (__VA_ARGS__); }
// --- example -------------------------------------------------------------
template <typename A, typename B>
auto plus(A const & a, B const & b) -> RETURNS(a + b)
// It can't be used with recursive definitions like here, though:
struct mul_ {
int operator()() const { return 1; }
template <typename A>
A operator()(A const & a) const { return a; }
// template <typename A, typename B, typename... C>
// auto operator()(A const & a, B const & b, C const &... c) const ->
// RETURNS(mul_()(a * b, c...))
// --> Error: invalid use of incomplete type mul_
// std::declval helps, but duplicates the multiplication part:
template <typename A, typename B, typename... C>
auto operator()(A const & a, B const & b, C const &... c) const ->
decltype(std::declval<mul_>()(a * b, c...)) {
return mul_()(a * b, c...);
}
};
constexpr mul_ mul = {}; // global function object
// --- example -------------------------------------------------------------
mul(); // 1
mul(10); // 10
mul(10, -20, 30.0); // -6000.0
// =============================================================================
// 7) Counted template recursion. The function "apply_tuple(f, t)" calls the
// function (function object) "f" with the elements of the tuple "t" as
// arguments. (To simplify things a bit, I omitted the perfect forwarding
// support in this example.)
//
// The count is tracked with a total number of iterations N, and the running
// index I. R is the precalculated result type.
namespace detail {
template <typename R, std::size_t N, std::size_t I=0>
struct apply_tuple {
template <typename F, typename T, typename... Args>
R operator()(F f, T const & t, Args const &... args) const {
typedef apply_tuple<R, N, I + 1> next;
return next()(f, t, args..., std::get<I>(t));
}
};
template <typename R, std::size_t N> struct apply_tuple<R, N, N> {
template <typename F, typename T, typename... Args>
R operator()(F f, T const &, Args const &... args) const {
return f(args...);
}
};
}
template <typename F, typename... T>
decltype(std::declval<F>()(std::declval<T const &>()...))
apply_tuple(F f, std::tuple<T...> const & t) {
typedef decltype(std::declval<F>()(std::declval<T const &>()...))
result;
return detail::apply_tuple<result, sizeof...(T)>()(f, t);
}
// --- example -------------------------------------------------------------
int f(int a, int b) { return a + b; }
apply_tuple(f, std::make_tuple(10, 20)); // 30
auto t = std::make_tuple(10, -20, 30.0);
apply_tuple(mul, t); // -6000.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment