39

Can I use template aliases as template template parameters?

template <template <typename...> class> struct foo {};

template <typename T> using simple_ptr = std::unique_ptr<T>;

foo<std::unique_ptr> a; // this doesn't work, std::unique_ptr has two parameters
foo<simple_ptr> b; // does this work?
Morwenn
  • 21,684
  • 12
  • 93
  • 152
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • 1
    I suppose the question should have rather been "should this work?" (gcc does not seem to support template aliases yet, so it's probably not possible to try it). – Jan Hudec Sep 06 '11 at 12:23
  • Are you looking for workaround in case if alias doesn't work? – Nawaz Sep 06 '11 at 12:32
  • @Nawaz: Well, turns out it does work, but if you post a workaround for pre-C++11 (or for any weird compiler that supports aliases but doesn't support this), I'll upvote it. – R. Martinho Fernandes Sep 06 '11 at 13:39

2 Answers2

26

Yes, it is apparently allowed. According to the latest draft of the upcoming standard I could find, it is stated that

A template-argument for a template template-parameter shall be the name of a class template or an alias template [...].

However, alias templates seems very seldomly supported at the moment, so you might have some trouble making it work with most compilers.

Luc Touraille
  • 79,925
  • 15
  • 92
  • 137
1

People who read the original question may be writing structs that use template template parameters as meta functions, as demonstrated in the listing below.

template <int T>
struct integer
{
        using value = T;
};

template <class T, class U, template <class...> class Function>
struct binary_op
{
        // Works for add_1, but not add_2
        using type = typename Function<T, U>::type;

        // Works for add_2, but not add_1
        using type = Function<T, U>;
};

template <class T, class U>
struct add_1;

template <int T, int U>
struct add_1<integer<T>, integer<U>>
{
        using type = integer<T + U>;
};

template <class T, class U>
using add_2 = typename add_1<T, U>::type;

add_1 and add_2 are both meta-functions, let's distinguish

  • add_1 as an example of nested typedef-style metafunction (which c++03 supported)
  • add_2 as an example of template alias-style metafunction (which requires c++11)

The binary_op struct can work either with template alias-style or nested typedef-style metafunctions, but not both. In this answer, I show how such TMP code can be rewritten to avoid this problem.

Suppose that you wish to apply a template template parameter Function to a parameter pack of values Ts.... To apply the metafunction, you need either

using type = Function<Ts...>; // template-alias style

or

using type = typename Function<Ts...>::type; // nested typedef style

It would be useful to have another generic metafunction that detects the kind of metafunction that was passed, and applys it accordingly.

The is_alias_metafunction function, which is implemented below, is a building block for such a facility:

#include <type_traits>

template <class... Ts>
struct sequence;

template <class T>
struct check
{
    static constexpr bool value = true;
};

template <
    template <class...> class Function,
    class                     S,
    class                     Check = void
>
struct is_alias_metafunction
{
    static constexpr bool value = true;
};

template <
    template <class...> class Function,
    class...                  Ts
>
struct is_alias_metafunction<
    Function,
    sequence<Ts...>,
    typename std::enable_if<
        check<typename Function<Ts...>::type>::value
    >::type
>
{
    static constexpr bool value = false;
};

Now, we can write a metafunction apply that applies a template template parameter Function to the parameter pack Ts..., regardless of whether Function is a template alias or a template struct.

template <
    bool                      IsAlias,
    template <class...> class Function,
    class                     S
>
struct apply_impl;

template <template <class...> class Function, class... Ts>
struct apply_impl<true, Function, sequence<Ts...>>
{
    using type = Function<Ts...>;
};

template <template <class...> class Function, class... Ts>
struct apply_impl<false, Function, sequence<Ts...>>
{
    using type = typename Function<Ts...>::type;
};

template <template <class...> class Function, class... Ts>
using apply = typename apply_impl<
    is_alias_metafunction<Function, sequence<Ts...>>::value,
    Function,
    sequence<Ts...>
>::type;

We can now use the apply metafunction as follows:

using type = apply<Function, Ts...>;

and it will abstract away the difference between 'legacy' metafunctions and modern (c++11) metafunctions.

sehe
  • 374,641
  • 47
  • 450
  • 633
void-pointer
  • 14,247
  • 11
  • 43
  • 61
  • You `is_alias` does something completely different from what you described. The problem you described is also bollocks. There is no problem, and therefore there is no need for a solution. – R. Martinho Fernandes Jul 18 '13 at 13:52
  • Wait. What. Could you maybe justify this wall of code prior to just embarking on reciting it? _"It would be useful to have a metafunction that determines whether a given template template parameter is a template alias"_ needs citation – sehe Jul 18 '13 at 13:53
  • @R.MartinhoFernandes,sehe Well, my thinking was that people who saw this question might want to write TMP code that works for both template aliases and template structs. If your library interface has structs that take template template parameters, your code will break for either template structs or template aliases without some level of indirection. – void-pointer Jul 18 '13 at 13:57
  • Tell me, what's the difference? Because what you're listing as a difference isn't a technical limitation, AFAICT. It's just a convention and has as such to do with ... convention. – sehe Jul 18 '13 at 14:00
  • 3
    @void-pointer: [It works fine](http://coliru.stacked-crooked.com/view?id=719a25d85b6e1ac097bead9a26d49952-93e6c6235a92d0c233f44beab03470ad). You don't have to disambiguate and there's no need for any extra `typename`s. Aliases and class templates behave identically as template template parameters. – Puppy Jul 18 '13 at 14:01
  • _"the difference between std::remove_reference_t and typename std::remove_reference::type, is the only thing I can think of. Which is convention."_ – sehe Jul 18 '13 at 14:05
  • @DeadMG You missed my point entirely. There's a difference between binding arguments to a template template parameter and applying the template template function in order to obtain a value. E.g. `Function` is different from `Function::type`. – void-pointer Jul 18 '13 at 14:06
  • @sehe Yes, it's convention. But now that template aliases are available, metafunctions will exist both as template structs with `type` typedefs and as template aliases. Isn't it a good idea to accommodate for both? – void-pointer Jul 18 '13 at 14:08
  • @void-pointer yes, that's not what is described on the answer (only vaguely what is in the code, but even then, not even there, because it doesn't test if something is an alias, but actually tests if something doesn't have a nested `type`). – R. Martinho Fernandes Jul 18 '13 at 14:10
  • @R.MartinhoFernandes According to standard convention, template aliases should directly evaluate to the desired type, while template structs should expose the result via the `type` typedef. While `is_alias` doesn't strictly determine whether a template template parameter is a template alias, it allows you to correctly obtain the result of invoking a template template parameter on some arguments. – void-pointer Jul 18 '13 at 14:13
  • 1
    @void-pointer A template alias != a type_trait. Your logic ***or*** naming is flawed. Possibly both. I'm revoking my encouraging +1 at least until you edit the answer to make it abundantly clear what you're doing. Which is ***not*** what it says in the text, right now. Hint: `follows_alias_traits_convention` or `implements_legacy_traits_convention` to give you ideas – sehe Jul 18 '13 at 14:23
  • @sehe [Here's](http://pastebin.com/VxBe3XJU) a concrete example of code that will not work without some level of indirection. – void-pointer Jul 18 '13 at 14:25
  • I edited the question to clarify what I'm talking about to people who seem to be confused as to what I'm trying to get at. – void-pointer Jul 18 '13 at 14:36
  • I'm not looking for examples. I know what you are talking about. I'm waiting for you to bring the text in line with what you're actually doing: unifying different types of _type functions_. Perhaps you should consider creating a helper `invoke_type_function`... It's all not very related to the question, but at least it would make sense. – sehe Jul 18 '13 at 14:39
  • 1
    @sehe Well, here is my reasoning: (1) the problem I talk about in the beginning of my answer is closely related to the original question; (2) a significant proportion of people who read the original question are likely to come across the problem I describe; and (3) my answer explains this problem and solves it. The people who downvoted my answer to oblivion must either disagree with (1), (2), or (3). If people continue to think that this answer is not useful, I'll vote to delete it. – void-pointer Jul 18 '13 at 14:49
  • @void-pointer I never downvoted it :/ I just thought the answer was oddly out of place and your original wording grossly inaccurate. I've **[suggested a few more wording changes](http://stackoverflow.com/posts/17725032/revisions)**, and most importantly a rename from the glaring misnomer `is_alias`. With that, I am willing to grant you +1 for effort, even though it should technically a new (self-answered) question, or a blog post, perhaps :) Cheers – sehe Jul 18 '13 at 15:56
  • @sehe Thanks for the editions --- you've made my intent clearer. I don't really care whether people upvote or downvote the answer; I just posted it because I thought that it's useful and relevant to the original question. If most people think that it shouldn't belong here, I have no problems with it being removed. – void-pointer Jul 18 '13 at 16:10