|
Boost : |
Subject: Re: [boost] C++11 Metaprogramming
From: Pyry Jahkola (pyry.jahkola_at_[hidden])
Date: 2012-04-01 09:31:21
Hi,
On 2012-04-01 02:12:24 +0000, Dave Abrahams said:
> I am on the C++Now 2012 schedule giving a talk on metaprogramming in
> C++11 (...) I'm sure y'all have come up with many neat tricks and
> techniques. If you'd care to share them here, that would be much
> appreciated.
Below are a few tricks I've used with varying success with the trunk
version of clang and libc++ with C++11 compilation turned on. Some
might be obvious, some not, but at least they are some improvement over
their C++03 counterparts.
The text turned out to be quite lengthy, so here's a link to a
syntax-colored and more reader-friendly Gist:
https://gist.github.com/2275320
See you all in Aspen!
/Pyry Jahkola
* * *
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
(IC true)
is_integral_constant<std::integral_constant<int,1>>::value; // true
(IC one)
* * *
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
* * *
-- Pyry Jahkola · http://pyrtsa.posterous.com pyry.jahkola_at_[hidden] · http://twitter.com/pyrtsa Attending C++Now! 2012
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk