Boost logo

Boost :

Subject: Re: [boost] C++11 Metaprogramming
From: Dave Abrahams (dave_at_[hidden])
Date: 2012-04-03 11:57:34


on Sun Apr 01 2012, Pyry Jahkola <pyry.jahkola-AT-iki.fi> wrote:

> 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

Looking forward to it!

>
> 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;
> };

Do the uses of constexpr here add anything w.r.t. plain const?

> 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

Cute.

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

Interesting formulation. I used:

template <class ...Ss>
struct append_;

template <class ...T1s>
struct append_<vector<T1s...> >
    : vector<T1s...> {};

template <class ...T1s, class ...T2s>
struct append_<vector<T1s...>, vector<T2s...> >
    : vector<T1s...,T2s...> {};

template <class ...T1s, class ...Ss>
struct append_<vector<T1s...>, Ss...>
    : append_<vector<T1s...>, typename append_<Ss...>::type> {};

Does the nested class template arrangement have any advantages?

> * * *
>
> 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)

Totally. I have used

  // RETURNS() is used to avoid writing boilerplate "->decltype(x) { return x; }" phrases.
  //
  // USAGE: auto function(<arguments>) RETURNS(<some-expression>);
  //
  // Note: we end with a unique typedef so the function can be followed
  // by a semicolon. If we omit the semicolon, editors get confused and
  // think we haven't completed the function declaration.
  #define RETURNS(...) -> decltype(__VA_ARGS__) { return (__VA_ARGS__); } typedef int RETURNS_CAT(RETURNS_, __LINE__)
  // Standard PP concatenation formula
  #define RETURNS_CAT_0(x, y) x ## y
  #define RETURNS_CAT(x, y) RETURNS_CAT_0(x,y)

Probably it's a good idea to incorporate noexcept:

#define RETURNS(...) noexcept(noexcept(decltype(__VA_ARGS__)(std::move(__VA_ARGS__)))) -> decltype(__VA_ARGS__) { return (__VA_ARGS__); } typedef int RETURNS_CAT(RETURNS_, __LINE__)

> 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_
> }
> };

Heh, try (*this) instead of mul_(). That works until you try to
incorporate noexcept as suggested above (at least on GCC 4.7). After
that you have to do something like this:

--8<---------------cut here---------------start------------->8---
struct mul_ {
    int operator()() const { return 1; }

    template <typename A>
    A operator()(A const & a) const noexcept(noexcept(A(std::move(a))))
    { return a; }

    // ****** workaround ******
    static mul_ get() noexcept { return mul_(); }

    template <typename A, typename B, typename... C>
    auto operator()(A const & a, B const & b, C const &... c) const
        RETURNS(get()(a * b, c...));
};
--8<---------------cut here---------------end--------------->8---

very annoying.

> 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

Isn't it a bit slicker to do this by creating an argument pack
of integers and expanding that with get<Is>(t)... ?

--8<---------------cut here---------------start------------->8---
template <class T, T I, class S> struct cons_c;

template <template <class T, T...> class S, class T, T I, T ...Is>
struct cons_c<T, I, S<T, Is...> >
  : S<T,I,Is...>
{};

template <class T, T ...Is>
struct vector_c { typedef vector_c type; };

template <std::size_t N>
struct count_
  : cons_c<std::size_t, N-1, typename count_<N-1>::type>
{};

template <>
struct count_<0>
  : vector_c<std::size_t>
{};

template <std::size_t N>
using count = typename count_<N>::type;

#include <tuple>

template <typename F, typename Tuple , std::size_t ...Is>
auto apply_tuple(F f, Tuple const & t, vector_c<std::size_t, Is...>)
  RETURNS(f(std::get<Is>(t)...));

template <typename F, typename ...T>
auto apply_tuple(F f, std::tuple<T...> const & t)
  RETURNS(apply_tuple(f, t, count<sizeof...(T)>()));
--8<---------------cut here---------------end--------------->8---

-- 
Dave Abrahams
BoostPro Computing
http://www.boostpro.com

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk