
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@iki.fi · http://twitter.com/pyrtsa Attending C++Now! 2012