3

I'm trying to write a Bind metaprogramming template helper metafunction that binds a template parameter to something.

I have a working implementation for simple template metafunctions:

template<typename T0, typename T1>
struct MakePair
{
    using type = std::pair<T0, T1>;
};

template<template<typename...> class TF, typename... Ts>
struct Bind
{
    template<typename... TArgs>
    using type = TF<Ts..., TArgs...>;
};

using PairWithInt = typename Bind<MakePair, int>::type;
static_assert(std::is_same<PairWithInt<float>, MakePair<int, float>>{}, "");

But what if MakePair's template arguments were template templates? Or simple numerical values?

template<template<typename> class T0, template<typename> class T1>
struct MakePair0
{
    using type = /*...*/;
};

template<template<typename...> class TF, template<typename> class... Ts>
struct Bind0 { /*...*/ }

// ...

template<int T0, int T1>
struct MakePair1
{
    using type = /*...*/;
};

template<template<int...> class TF, int... Ts>
struct Bind1 { /*...*/ }

A lot of unnecessary repetition. It gets unmanageable if template arguments are mixed between types, template templates, and integral constants.

Is something like the following piece of code possible?

template<template<ANYTHING...> class TF, ANYTHING... Ts>
struct BindAnything
{
    template<ANYTHING... TArgs>
    using type = TF<Ts..., TArgs...>;
};

ANYTHING would accept types, template templates, template template templates, integral values, etc...

Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • 1
    What is the benefit of something like this over simply writing out the template? It is all static/compile time anyway. – BitTickler Aug 17 '15 at 17:53
  • Composition. Check out this use-case I just written that I came across while writing some code. [Example pastie](http://pastie.org/10357043). (tl;dr: `Rename, Bind>`) – Vittorio Romeo Aug 17 '15 at 18:00

2 Answers2

9

When I'm doing serious metaprogramming, I turn everything into types.

template<class T>struct tag{using type=T;};
template<class Tag>using type_t=typename Tag::type;

template<template<class...>class> struct Z {};
template<class Z, class...Ts>
struct apply {};
template<template<class...>class z, class...ts>
struct apply< Z<z>, ts... >:
  tag< z<ts...> >
{};
template<class Z, class...Ts>
using apply_t = type_t< apply<Z, Ts...> >;

now we pass template<?> foo around as Z<foo>, and it is now a type.

Similar things can be done for constants, using std::integral_constant<T, t> (and easier to use aliases of same), or template<class T, T* p> struct pointer_constant {};, by turning them into types.

Once everything is a type, your metaprogramming becomes more uniform. Templates just become a kind of type on which apply_t does things to.

There is no way in C++ to have a template argument that can be a type, a value or a template. So this is about the best you can get.

templates not written for the above pattern need to be wrapped up, and their arguments "lifted" to being types. As an example:

template<class T, class t>
using number_constant = std::integral_constant< T, t{} >;
using number_constant_z = Z<number_constant>;

has had its arguments "lifted" from values to types, and then it has been wrapped with a Z to turn itself into a type.

Bind now reads:

template<class z, class... Ts>
struct Bind {
  template<class... More>
  using type_base = apply_t< z, Ts..., More... >;
  using type = Z<type_base>;
};
template<class Z, class...Ts>
using Bind_t = type_t<Bind<Z,Ts...>>; // strip ::type
using Bind_z = Z<Bind_t>; // quote into a Z<?>

and Bind_z is a type wrapping a template that returns a wrapped template, and takes a type that wraps a template as its first argument.

To use it:

template<class...>struct types{using type=types;};
using types_z=Z<types>;

template<class...Ts>
using prefix =apply_t< Bind_z, types_z, Ts... >;
using prefix_z = Z<prefix>;

prefix_z takes a set of types, and generates a factory of types<?...> that will contain the prefix Ts... first.

apply_t< apply_t< prefix_z, int, double, char >, std::string >

is

types< int, double, char, std::string >

live example.

There is another fun approach: do metaprogramming in functions:

template<template<class...>class z, class...Ts>
constexpr auto apply_f( Z<z>, tag<Ts>... )
-> tag<z<Ts...>> { return {}; }

here, types are represented by values of type tag<t>, templates a Z<z> and values as std::integral_constant<?>.

These two:

template<class T>
constexpr tag<T> Tag = {};
template<template<class...>class z>
constexpr Z<z> Zag = {};

give you ways to get values that represent types and templates respectively.

#define TYPEOF(...) type_t<decltype(__VA_ARGS__)>

is a macro that moves from an instance of a tag to type type in the tag, and Tag<?> moves from a type to an instance of a tag.

TYPEOF( apply_f( apply_f( Zag<prefix>, Tag<int>, Tag<double>, Tag<char> ), Tag<std::string> ) )

is

apply_t< apply_t< prefix_z, int, double, char >, std::string >

strange, but can be interesting.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • *"There is no way in C++ to have a template argument that can be a type, a value or a template."* Overloading function templates can do this with one template parameter. This requires `decltype` clutter, though. – dyp Aug 17 '15 at 18:17
  • 1
    @dyp true, and we could go boost hana-style, doing metaprogramming as `constexpr` functions. But even there, having `Z` is useful, because it lets you pass a template as a value. The overload solution you talk about ends up still having exponential explosion -- how do you write a function overload that takes a template as its first template argument, each argument of which (the template passed to the function) can be a template, a size_t, an int, a pointer to a function, or a type? You really cannot... – Yakk - Adam Nevraumont Aug 17 '15 at 18:18
  • Ad exponential explosion: That's why I said "one template parameter" :) It can be used to write a generic adapter: `decltype(adapt())`, where `X` can be a type, template, or value (or a function, but no overload set, of a fixed set of signatures). But due to the `decltype` clutter, it's probably better to write three separately named adapters. – dyp Aug 17 '15 at 19:06
  • @dyp Suppose `X` is `template` Another `X` is a `template`. Another is a `template`. There is an infinitely countable number of `template...>` signatures, and there is no way to talk about them uniformly to pass to `adapt`. You have to make your templates into uniform `template`. I'll admit a macro of `#define ADAPT(...) decltype( adapt<__VA_ARGS__>())` could make the quoting of constants and types more uniform (and even `template`). Amusingly, this makes the hana-style more tempting, as that kills `decltype`. – Yakk - Adam Nevraumont Aug 17 '15 at 19:10
  • 1
    Once we get constexpr lambdas, using objects might even become a syntactically pleasing approach: http://melpon.org/wandbox/permlink/Wm9vwW3qv8JLSApZ -- wait, we don't even need constexpr lambdas for that, I think. The entire computation is in an unevaluated context. – dyp Aug 17 '15 at 19:20
  • I didn't mean to suggest that you can adapt templates with non-type template parameters using function overloads. However, you can write your `Z` or my `tag` or Barry's `quote` as `decltype(adapt())`, while generalizing `adapt` to work not only for class templates (with type parameters), but also for values and types. – dyp Aug 17 '15 at 19:28
  • @dyp `TYPEOF` should take `(...)` to deal with the C preprocessor not understanding `< int, double >` as being part of one argument. But yes, that kind of function-style metaprogramming is quite fun. It being constexpr means you can keep things as tags instead of types for longer and do multi-line computation, however. I guess `#define CONSTEXPR(...) tag` also lets you move back from non-`constexpr` expression, to a `constexpr` value. – Yakk - Adam Nevraumont Aug 17 '15 at 19:37
  • Ah, right. I did wonder shortly why you'd use `...` for a single parameter. gcc does seem to support constexpr lambdas, but has issues with lambdas in variable templates. I thought it might be a good idea to use function objects everywhere, which makes `tag` the identity lambda `id` (returning some type wrapper, of course). And then came up with `TAGOF`, which is quite similar to your `CONSTEXPR`. http://melpon.org/wandbox/permlink/fcCsFNZ1lf3FL9TW – dyp Aug 17 '15 at 19:47
  • @dyp `template constexpr std::is_same operator== (tag, tag) { return {}; }` is better than your `==`. Start thinking with portals^H^H^H^H^H^H^H types! `return` to the `{}` from which you came. Wait, no, that's a balrog. The `constexpr` is a lie? – Yakk - Adam Nevraumont Aug 17 '15 at 19:56
  • o.O What? Did Zalgo escape from your latest answer? Anyway, I'll consider that once we've fixed `integral_constant` to use operators properly (IIRC hana does that already). Otherwise, the definition of `!=` won't be DRY. – dyp Aug 17 '15 at 20:05
4

I think you're looking for quote and map here. First, you want something that given a "metafunction class" and a sequence of arguments gives you a new type:

template <typename MCls, typename... Args>
using map = typename MCls::template apply<Args...>;

As the implementation here suggests, a metafunction class is one that has an member alias template named apply.

To turn a class template into a metafunction class, we introduce quote:

template <template <typename...> class C>
struct quote {
    template <typename... Args>
    using apply = C<Args...>;
};

The above is sufficient to do something like:

using T = map<quote<std::tuple>, int, char, double>;

to yield the type:

std::tuple<int, char, double>

In your example, we could write:

using P = map<quote<MakePair>, int, char>::type; // std::pair<int, char>

but I would instead prefer to make MakePair a metafunction class directly:

struct MakePair2 {
    template <typename T, typename U>
    using apply = std::pair<T, U>;
};

using P = map<MakePair2, int, char>; // also std::pair<int, char>

that avoids the extra ::type.

Consistently use the concepts of metafunction (a type with a member typedef named type, e.g. map) and a metafunction class (a type with a member template alias named apply, e.g. quote) and use only those concepts throughout your metaprogramming code. Values and class templates are second-class citizens.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • So, my `Z` is equivalent to `quote` and my `apply_t` to your `map`, but probably you are using more standard names (is `map` haskell's `fmap` for monads? Or is it `>>=`?). The difference is that your `map` relies on `T::apply...>` existing, and mine relies on use of `Z` (and only `Z`) to wrap a template (unless I upgrade my `apply_t` to also detect `T::apply...>`) – Yakk - Adam Nevraumont Aug 17 '15 at 18:39
  • @Yakk Mostly. I guess main difference is `map` works on any metafunction class whereas `apply_t` only works on `Z`s. – Barry Aug 17 '15 at 18:41
  • @Yakk Haskell has been on my "someday, I will learn this" list for years, but it's basically `fmap`. I'm not entirely sure what `>>=` does so can't say one way or the other. – Barry Aug 17 '15 at 18:42
  • *nod*, and it makes it a bit easier to make direct metafunctions rather than having to do the 3 steps my system requires (first, a structure that generates a type, then a `type_t` unwrapped, then a `Z` wrapper). I don't like the name of `map` however: to me, `map< Z>, list >` should be `list, Z, Z>`, not `Z< list >`. – Yakk - Adam Nevraumont Aug 17 '15 at 18:50
  • 1
    @Yakk `apply` would probably be a better name. But I'm sticking with `quote` over `Z` :-P – Barry Aug 17 '15 at 18:54
  • I'm not entirely sure if unpacking a parameter pack into an alias template which does not take a parameter pack is guaranteed to work, see http://wg21.cmeerw.net/cwg/issue1430 (since both g++ and clang++ accept your code, I guess it's not "dependent"?) – dyp Aug 17 '15 at 18:58
  • @dyp so `MakePair2` should maybe be illegal, but `quote` would be legal because it translates marshals `template using MakePair_t=typename MakePair::type` through a `templateclass` parameter? – Yakk - Adam Nevraumont Aug 17 '15 at 21:07
  • I don't quite see how `MakePair2` itself should be illegal; it doesn't use any parameter packs. `quote` both tries to match the alias template against a `templateclass` and then fills the arguments from a parameter pack. This could be illegal, but it doesn't have the exact same problem that the example in CWG 1430 has: the alias template does not alter the argument list. So I'm not sure if 1430 applies. (Essentially I'm referring to http://stackoverflow.com/q/32014484/ ) – dyp Aug 17 '15 at 21:14