Can I use template aliases as template template parameters?
Asked Answered
T

2

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?
Tenner answered 6/9, 2011 at 12:0 Comment(3)
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).Openandshut
Are you looking for workaround in case if alias doesn't work?Soilure
@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.Tenner
P
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.

Polaroid answered 6/9, 2011 at 12:35 Comment(0)
P
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.

Preternatural answered 18/7, 2013 at 13:36 Comment(17)
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.Tenner
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 citationParade
@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.Preternatural
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.Parade
@void-pointer: It works fine. You don't have to disambiguate and there's no need for any extra typenames. Aliases and class templates behave identically as template template parameters.Prokopyevsk
"the difference between std::remove_reference_t<T> and typename std::remove_reference<T>::type, is the only thing I can think of. Which is convention."Parade
@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<int> is different from Function<int>::type.Preternatural
@Parade 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?Preternatural
@Preternatural 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).Tenner
@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.Preternatural
@Preternatural 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<TTA> or implements_legacy_traits_convention<TTA> to give you ideasParade
@Parade Here's a concrete example of code that will not work without some level of indirection.Preternatural
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.Preternatural
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.Parade
@Parade 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.Preternatural
@Preternatural 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, 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 :) CheersParade
@Parade 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.Preternatural

© 2022 - 2024 — McMap. All rights reserved.