Matching Alias Template as Template Argument

Matching alias template as template argument

This is CWG issue 1286. The question is: are alias and test equivalent? There used to be an example in [temp.type] which suggested that y and z have the same type here:

template<template<class> class TT> struct X { };
template<class> struct Y { };
template<class T> using Z = Y<T>;
X<Y> y;
X<Z> z;

The example was corrected as part of CWG defect 1244 - which indicated correctly that there is no wording in [temp.alias] that actually specifies that alias templates are equivalent to the templates they alias. The only wording there refers to equivalence of alias template specializations:

When a template-id refers to the specialization of an alias template, it is equivalent to the associated type obtained by substitution of its template-arguments for the template-parameters in the type-id of the alias template.

The intent is apparently that y and z do have the same type in this example, meaning that Z and Y are actually equivalent. But unless and until the wording in the resolution is adopted, they are not. Today, alias and test are not equivalent but alias<int> and test<int> are. This means that is_specialization_of<alias, alias<int>> is is_specialization_of<alias, test<int>>, where alias is unique from test, which would not match your partial specialization and thus be false_type.

Moreover, even with the adoption of the wording in #1286, test and alias are still not equivalent for the obvious reason that test takes two template parameters and alias takes one template parameter. The example in the resolution wording mimics your example and clarifies the intent here:

template<typename T, U = T> struct A;

// ...

template<typename V>
using D = A<V>; // not equivalent to A:
// different number of parameters

Matching template aliases as template template parameters

You are this close! You only need to use a class instead of an alias:

template<typename ARG, typename... ARGS>
struct first_of { using type = ARG; };

See live example.

You cannot use an alias directly because

 first_of<_1,_2,_3,_4>

is immediately substituted for _1, which is not of the form expected by eval_impl.

I don't find this is a limitation because we usually define template functions in the above form, and then define additional aliases like

template<typename ARG, typename... ARGS>
using first_of_t = typename first_of<ARG, ARGS...>::type;

for easier use. So we usually have both; you'll have to use the former with eval.


Attempt 2. Also note that direct use of an alias is possible without placeholders at all:

template<template<typename...> class F, typename... ARGS>
using alias_eval = F<ARGS...>;

in which case you can say

using alias_call = alias_eval<first_of_t, bool, float, char, int>;

as in your first attempt. See updated example. But I guess this is of no use because you intend to use placeholders in a less trivial way.


Attempt 3. Yet another option is to delay substitution of the alias, e.g.

template<template<typename...> class F, typename... PLACEHOLDERS>
struct holder {};

template<typename EXPRESSION, typename... ARGS>
struct holder_eval_impl;

template<template<typename...> class F, typename... PLACEHOLDERS, typename... ARGS>
struct holder_eval_impl<holder<F, PLACEHOLDERS...>, ARGS...> :
public F<ARGS...> {};

template<typename EXPRESSION, typename... ARGS>
using holder_eval = typename holder_eval_impl<EXPRESSION, ARGS...>::type;

which is very close to your intended syntax

using holder_call =
holder_eval<holder<first_of,_1,_2,_3,_4>, bool, float, char, int>;

especially if you use a short name for holder. Again, live example.

Type aliases and template template argument deduction

Yes, that is intentional. Alias templates are indeed, as you said, somewhat of a "second class citizen". To start with, alias templates cannot be specialized, that's a real big hint right there.

Now, their "lower grade" as evident in your example is all about [temp.alias]/2:

When a template-id refers to the specialization of an alias
template, it is equivalent to the associated type obtained by
substitution of its template-arguments for the template-parameters
in the type-id of the alias template. [ Note: An alias template name
is never deduced. — end note ]

The above means, that when you write Matched<Alias<int>>, since Alias<int> refers to a specialization of the alias template, it's equivalent to directly writing Matched<std::tuple<int,int>>. And it's quite obvious why that doesn't match the specialized variable template.

It's not an oversight, nor is it going to be fixed. Alias templates are there to provide a shorthand for more complex template expressions. And you wouldn't want the wrong overload to be called, or the wrong template specialization instantiated, because you used a shorthand instead of the whole complex expression.

Equality of template aliases

I try to create template alias which cannot be distinguished from original.

I don't think this is currently possible. There are (unfortunately) no template aliases, there are only alias templates. And an alias template is always a template of its own [temp.alias]/1. A specialization of an alias template is equivalent to the type you get by substituting the template arguments into the alias template, but the alias template itself is not an alias for another template [temp.alias]/2. I would consider GCC letting your second static_assert pass a bug in GCC…

As pointed out by @HolyBlackCat in the comment above, there is a related question and answer which points to numerous related CWG issues. One issue in particular (CWG 1286) would seem to suggest that there is desire to allow an alias template to itself be equivalent to the template it refers to under certain circumstances. However, it does not seem that the proposed resolution has been adopted due to concerns raised later. The relevant wording in the current standard draft ([temp.alias] and [temp.type]) appears to be unchanged from C++11…

How can a type alias with using specify a template template argument dependent on a template argument?

You need to specify that type is a template:

template<class T>
using special = templ< T::template type>;

This is needed because T::type is dependent on the template parameter T.

See also Where and why do I have to put the “template” and “typename” keywords?

C++11 alias template as template template argument in specialization?

I think that 14.5.7 Alias templates:

When a template-id refers to the specialization of an alias template, it is equivalent to the associated type obtained by substitution of its template-arguments for the template-parameters in the type-id of the alias template.

would apply, so X<Z> would be interpreted as Z* as soon as X is known to be an alias template. The syntax for a partial specialisation does indeed use the template-id grammar rule. However, template argument substitution takes place as the last step in template argument deduction, only once all template arguments have been deduced.

14.5.5.1 Matching of class template partial specializations [temp.class.spec.match]

2 A partial specialization matches a given actual template argument list if the template arguments of the partial specialization can be deduced from the actual template argument list (14.8.2).

14.8.2 Template argument deduction [temp.deduct]

5 When all template arguments have been deduced or obtained from default template arguments, all uses of template parameters in the template parameter list of the template and the function type are replaced with the corresponding deduced or default argument values.

Without template argument substitution of X, Z cannot be deduced. So, the specialisation does not match.

C++17 alias template with template class default arguments

Are there any workarounds to still allow the "<>" to be dropped?

A possible workaround may be a transparent inheritance:

template<int LENGTH = 1>
struct MyStruct{ int arr[LENGTH]; };

template<int LENGTH = 1>
struct MyAlias : MyStruct<LENGTH> { };

int main()
{
MyAlias<2> a;
MyAlias<> b;
MyAlias c;
return 0;
}

A (possibly) dangerous side-effect is however that the base class has no virtual destructors, leading to a possible memory leak if used polymorphically.

That seems like unexpected behavior to me.

Class template argument deduction, which enables the feature you are trying to use, appears to require a name of a real class template, not that of an template alias. What the compiler basically does is something like transforming

MyStruct obj;

to

template <int LENGTH=1>
MyStruct<LENGTH> f() { return MyStruct<Length>{ }; }

auto obj = f();

However, for aliases, you could do something like this:

template <int LENGTH = 1>
using MyAlias = MyStruct<LENGTH + 1>;

The above transformation totally would miss that "+ 1" if it just replaced the name MyAlias by MyStruct, implying there is no trivial solution to this problem - but by this time nowhere in the standard this case is handled, so its understandable it will not compile.

Template alias of a more generic template using externally defined template type as a template parameter

Instead of listing your template arguments as Return_T, Args_T... I would follow Signals2 example and use a single parameter of function type like Return_T(Args_T...) in SignalHandler.

Then we simply change Functional_T to a template template parameter.

template<template <typename> typename Functional_T, typename Signature_T>
using SignalHandler = Functional_T<Signature_T>;

Now the first parameter passed to SignalHandler must be a template that can be instatiated with 1 template parameter. Since you are using c++17 we can pass templates with more parameter as well, as long as they have default values.

To get Return_T and Args_T... in ISignalEmitter we use partial specialization.

template<typename Connection_T, template <typename> typename Functional_T, typename Signature_T>
struct ISignalEmitter; // Base declaration to match the specialization against

template<typename Connection_T, template <typename> typename Functional_T, typename Return_T, typename ...Args_T>
struct ISignalEmitter<Connection_T, Functional_T, Return_T(Args_T...)> // Matching Return_T and Args_T against Signature_T
{
protected:
virtual Return_T trigger(Args_T ...args) = 0;
public:
virtual void disconnectAll() = 0;
virtual SignalConnection<Connection_T> onSignal(SignalHandler<Functional_T, Return_T(Args_T...)> &signalHandler) = 0;
virtual void cancelOnSignal(const SignalConnection<Connection_T> &connection) = 0;
};

At this point you can define BoostSignals2SignalHandler like so

template<typename Signature_T>
using BoostSignals2SignalHandler = SignalHandler<boost::signals2::slot, Signature_T>;

using MySignalHandler = BoostSignals2SignalHandler<void()>;


Related Topics



Leave a reply



Submit