Boost logo

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