|
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