template <...Xs>
struct X;
template <template <typename...> class TT>
struct X<TT> {};
template <typename T>
struct X<T> {};
template <typename T, T I>
struct X<I> {};
template <...Xs>
struct X;
template <template <typename...> class TT>
struct X<TT> {};
template <typename T>
struct X<T> {};
template <typename T, T I>
struct X<I> {};
// Or the last one could be stated as this, now
// that auto can be used in templates parameters:
template <auto I>
struct X<I> {};
No official proposal yet but I already started a thread about it here: https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/sZZ-Weg-kUA
Also your syntax doesn't allow for non-variadic generic template parameter.
// Can't define a template with a placeholder TPT
template <placeholder>
struct X;
template <template <typename...> class TT>
struct X<TT> {};
template <typename T>
struct X<T> {};
template <typename T, T I>
struct X<I> {};
template <auto I>
struct X<I> {};
// Not a template specialization but a template with a single UTPT
template <>
struct X0;
// A template with two consecutive UTPT
template <,>
struct X1;
// A template with constant, a UTPT, and a type
template <auto I, , typename T>
struct X2;
// Not a template specialization but a template with a single UTPT
template <placeholder>
struct X0;
// A template with two consecutive UTPT
template <placeholder, placeholder> struct X1;
// A template with constant, a UTPT, and a type
template <auto I, placeholder, typename T> struct X2;
// Not a template specialization but a template with a single UTPT
template </*placeholder*/>
struct X0;
// A template with two consecutive UTPT
template </*placeholder*/, /*placeholder*/>
struct X1;
// A template with constant, a UTPT, and a type
template <auto I, /*placeholder*/, typename T>
struct X2;
// Can't define a template with a placeholder TPT
template <placeholder>
struct X;
One further note is that UTPTs are unnamed, the only way to access that parameter is through specialization, at which point it becomes a real TPT, which if named, can be referenced.
One further note is that UTPTs are unnamed, the only way to access that parameter is through specialization, at which point it becomes a real TPT, which if named, can be referenced.I've reconsidered this. Instead of having a contextual keyword, we can use any non-keyword or blank if the parameter is not referenced. If a single non-keyword is used, then this becomes its name, and is how you can reference a UTPT. This is important if that parameter is to be handed off to another template.
template<placeholder... X>
requires (sizeof...(X) % 2 == 0) && (even_are_types_and_odd_string_literals<X...>())
struct named_tuple : even_into_tuple_t<X...>
{
template<auto& literal>
decltype(auto) get() noexcept { static_assert(literal_exists<literal>()); ... }
};
named_tuple<std::string, "name", int, "age"> void get_student();
auto x = get_student().get<"name">();
template <template <typename...> class OP, typename PARAMS, typename...Ts>
struct tbind_impl;
template <template <typename...> class OP, typename...Ss>
struct tbind_impl<OP, std::tuple<Ss...>>
{
template<typename...Us>
using ttype = OP<Ss...>;
};
template <template <typename...> class OP, typename T, typename...Ts, typename...Ss>
struct tbind_impl<OP, std::tuple<Ss...>, T, Ts...>
{
template<typename...Us>
using ttype = typename tbind_impl<
OP
, std::tuple<Ss..., T>
, Ts...
>::template ttype<Us...>;
};
template <template <typename...> class OP, size_t I, typename...Ts, typename...Ss>
struct tbind_impl<OP, std::tuple<Ss...>, std::integral_constant<size_t, I>, Ts...>
{
template<typename...Us>
using ttype = typename tbind_impl<
OP
, typename std::tuple<
Ss...
, typename std::tuple_element<
I
, std::tuple<Us...>
>::type
>
, Ts...
>::template ttype<Us...>;
using t0 = std::integral_constant<size_t, 0>;
using t1 = std::integral_constant<size_t, 1>;
using t2 = std::integral_constant<size_t, 2>;
// ... other placeholders
template <template <typename...> class OP, typename...Ts>
struct tbind : tbind_impl<OP, std::tuple<>, Ts...>
{};
template <...>
struct UTPT_list {};
template <size_t I, size_t N, ...Ts>
UTPT_element_impl;
template <size_t I, size_t N, T, ...Ts>
UTPT_element_impl<I, N, Ts...>
: UTPT_element_impl<I+1, N, Ts...>
{};
template <size_t N, auto V, ...Ts>
UTPT_element_impl
{
static constexpr auto item = V;
};
template <size_t N, typename T, ...Ts>
UTPT_element_impl
{
using item = T;
};
template <size_t N, template<...> TT, ...Ts>
UTPT_element_impl
{
template<...Us>
using item = T<Us...>;
};
template <size_t N, ...Ts>
UTPT_element : UTPT_element_impl<0, N, Ts...> {};
template <template <...> class OP, typename PARAMS, ...Ts>
struct tbind_impl;
template <template <...> class OP, ...Ss>
struct tbind_impl<OP, UTPT_list<Ss...>>
{
template<...Us>
using ttype = OP<Ss...>;
};
template <template <...> class OP, T, ...Ts, ...Ss>
struct tbind_impl<OP, UTPT_list<Ss...>, T, Ts...>
{
template<...Us>
using ttype = typename tbind_impl<
OP
, UTPT_list<Ss..., T>
, Ts...
>::template ttype<Us...>;
};
template <template <...> class OP, size_t I, ...Ts, ...Ss>
struct tbind_impl<OP, UTPT_list<Ss...>, std::integral_constant<size_t, I>, Ts...>
{
template<...Us>
using ttype = typename tbind_impl<
OP
, typename UTPT_list<
Ss...
, UTPT_element<I, Us...>::item
>
, Ts...
>::template ttype<Us...>;
using t0 = std::integral_constant<size_t, 0>;
using t1 = std::integral_constant<size_t, 1>;
using t2 = std::integral_constant<size_t, 2>;
// ... other placeholders
template <template <...> class OP, ...Ts>
struct tbind : tbind_impl<OP, UTPT_list<>, Ts...>
{};
// limited to templates that consist of only types
bool result = tbind<is_convertible, t1, long>::ttype<int>::value
// result == true
// these would result in a double type
tbind<UTPT_element, 2, t0, t1, t2>::ttype<int, float, double>>::item x = 1.3;
tbind<UTPT_element, t0, t1, t2, double>::ttype<2, int, float>>::item x = 1.3;
On Sunday, December 10, 2017 at 4:49:36 PM UTC+1, adrian....@gmail.com wrote:One further note is that UTPTs are unnamed, the only way to access that parameter is through specialization, at which point it becomes a real TPT, which if named, can be referenced.I've reconsidered this. Instead of having a contextual keyword, we can use any non-keyword or blank if the parameter is not referenced. If a single non-keyword is used, then this becomes its name, and is how you can reference a UTPT. This is important if that parameter is to be handed off to another template.Adding a new keyword to the language is a pain. This is not big enough to grant such a pain.As for the empty solution is not a great solution because the intent is absolutely unclear, it's not easy to read and is really not C++-like.
Also you have decided without justification that such a placeholder can't be used.
I don't see why as a general-purpose template parameter could be quite useful.It could be used to implement, for instance, named tuples for instance:
template<placeholder... X>
requires (sizeof...(X) % 2 == 0) && (even_are_types_and_odd_string_literals<X...>())
struct named_tuple : even_into_tuple_t<X...>
{
template<auto& literal>
decltype(auto) get() noexcept { static_assert(literal_exists<literal>()); ... }
};
named_tuple<std::string, "name", int, "age"> void get_student();
auto x = get_student().get<"name">();
And coupled with the meta_class proposal could allow to create real members using the non-type argument as name.
template <size_t N, template<...> TT, ...Ts>
UTPT_element_impl
{
template<...Us>
using item = T<Us...>;
};
template <size_t N, template<...> class TT, ...Ts>
UTPT_element_impl
{
template<...Us>
using item = TT<Us...>;
};
Current usage would be:
// limited to templates that consist of only types
bool result = tbind<is_convertible, t1, long>::ttype<int>::value
// result == true
Current usage would be:
// limited to templates that consist of only types
bool result = tbind<is_convertible, t0, long>::ttype<int>::value
// result == true
A UTPT can only be passed to another template or specialized on, as its actual meaning is not understood at that context level. Once passed to another template, or is specialized on to become a DTPT (defined template parameter type) can it be actually be used.
A UTPT can be used when passing to another template, used in specialized, or other contexts where the TPT is not needed such as with size...(), as its actual TPT is not understood at that context level. Once passed to another template, or is specialized on to become a DTPT (defined template parameter type) can it be actually be used.
Adding a new keyword to the language is a pain. This is not big enough to grant such a pain.
As for the empty solution is not a great solution because the intent is absolutely unclear, it's not easy to read and is really not C++-like.
// A replacement for std::tuple for UTPTs
template <...>
struct UTPT_list {};
// A replacement for std::tuple_element for UTPTs
template <size_t I, size_t N, ...Ts>
UTPT_element_impl;
template <size_t I, size_t N, T, ...Ts>
UTPT_element_impl<I, N, Ts...>
: UTPT_element_impl<I+1, N, Ts...>
{};
template <size_t N, auto V, ...Ts>
UTPT_element_impl
{
static constexpr auto item = V;
};
template <size_t N, typename T, ...Ts>
UTPT_element_impl
{
using item = T;
};
template <size_t N, template<...> class TT, ...Ts>
UTPT_element_impl
{
template<...Us>
using item = TT<Us...>;
};
// limited to templates that consist of only types
bool result = tbind<is_convertible, t0, long>::ttype<int>::value
// result == true
// Note that the name UTPT here is optional, just like it would be for a regular template to be specialized
template <UTPT>
struct is_template : std::false_type {};
// This is a specialization that can take a template that can take any number of UTPT.
template <template <...> TT>
struct is_template<TT> : std::true_type {};
Some time ago I participate in discussion of similar feature. One difference was that was using `using` keyword to introduce unknown type of template parameters. I think use in this context will be consisting with rest of language because you can `using` not only types but function too.
template<template T>
constexpr auto func()
{
if constexpr (requires() { T; })
return T;
else if constexpr (requires() { typename T; })
return typename T{};
else
return template T<>{};
}
template<template T>
constexpr void x()
{
func<T>(); //is pre-parsed as a dependent template argument. T is then substituted on instantiation by its actual value.
}
int main(){
x<42>(); //ok
x<int>(); //ok
}
I'm sorry but more of the same is not going to help here.A library solution doesn't help.std::integral_constant & affiliated solutions are generally extremely clunky and require the user of to work for the solution to be functional.
I actually disagree on your statement that's it's not possible.
The only issue with it is C++ parsing and there is a simple solution.I proposed it in an earlier thread, that said "universal template parameter" ought to behave as dependent members:-implicitly value.-requires prefix "typename" if used, or tried to be used, as type.-requires prefix "template" if is a template template parameter.
With the difference that when giving such an argument to a template instantiation list the value/type must be substituted for the parsed one.Ex:
template<template T>
constexpr auto func()
{
if constexpr (requires() { T; })
return T;
else if constexpr (requires() { typename T; })
return typename T{};
else
return template T<>{};
}
template<template T>
constexpr void x()
{
func<T>(); //is pre-parsed as a dependent template argument. T is then substituted on instantiation by its actual value.
}
int main(){
x<42>(); //ok
x<int>(); //ok
}Having seen from close GCC implementation I believe it's technically doable.
On the syntax front I disagree on the use of "using" and the justification behind it."using" has one meaning : name substitution. With namespaces, classes and functions. NOT values."template" is actually more appropriate.Template only means canvas, to be substituted by.You can template any kind of definition.Using "template" as a universal template argument seems more intuitive than "using"."template" in a template parameter list would simply mean:a parameter-less template, something to be resolved as template doesn't infer what is actually templated.
On Thursday, December 14, 2017 at 4:31:03 PM UTC+8, bastie...@gmail.com wrote:I'm sorry but more of the same is not going to help here.A library solution doesn't help.std::integral_constant & affiliated solutions are generally extremely clunky and require the user of to work for the solution to be functional.If it is required to specify various types of metadata to a class template, there seems to be no necessity to use other utilities, e.g.:template <class ENUM_T, ENUM_T E>class foo {// ...};
I actually disagree on your statement that's it's not possible.I did not seem to say something "not possible" in my statement. What do you mean by the "it"?
The only issue with it is C++ parsing and there is a simple solution.
I proposed it in an earlier thread, that said "universal template parameter" ought to behave as dependent members:-implicitly value.-requires prefix "typename" if used, or tried to be used, as type.-requires prefix "template" if is a template template parameter.
That will be a new language feature. However, the motivation of it seems to be insufficient, because:1. The idea will carry unnecessary ambiguation to the semantics of the template, and further reduce the maintainability of the product.
2. I could not find a case that your idea could give full play to the advantages.
named_tuple<int, "age", string, "name"> x;
x.get<"name">() = get_user_name();
static_assert(std::is_same_v<std::string &, decltype(std::get<1>())>);
With the difference that when giving such an argument to a template instantiation list the value/type must be substituted for the parsed one.Ex:
template<template T>
constexpr auto func()
{
if constexpr (requires() { T; })
return T;
else if constexpr (requires() { typename T; })
return typename T{};
else
return template T<>{};
}
template<template T>
constexpr void x()
{
func<T>(); //is pre-parsed as a dependent template argument. T is then substituted on instantiation by its actual value.
}
int main(){
x<42>(); //ok
x<int>(); //ok
}Having seen from close GCC implementation I believe it's technically doable.I think this case properly illustrates the syntax you designed, but could not prove the reasonableness of your idea, because there seems to be little motivation for a user to design the function template `func` defined above, and thus this case seems to be meaningless.
On the syntax front I disagree on the use of "using" and the justification behind it."using" has one meaning : name substitution. With namespaces, classes and functions. NOT values."template" is actually more appropriate.Template only means canvas, to be substituted by.You can template any kind of definition.Using "template" as a universal template argument seems more intuitive than "using"."template" in a template parameter list would simply mean:a parameter-less template, something to be resolved as template doesn't infer what is actually templated.From my point of view, the template is a container of metadata, and "type" is a the basic type of metadata because any "value metadata" has a type, and that "type" is also a part of metadata. Thus, I think, the type of the "value metadata" should not be ignored in a template.
On Thursday, December 14, 2017 at 11:24:41 AM UTC+1, Mingxin Wang wrote:On Thursday, December 14, 2017 at 4:31:03 PM UTC+8, bastie...@gmail.com wrote
2. I could not find a case that your idea could give full play to the advantages.
I can give you one that's would be simple to use and useful : named_tuple.
named_tuple<int, "age", string, "name"> x;
x.get<"name">() = get_user_name();
static_assert(std::is_same_v<std::string &, decltype(std::get<1>())>);
named_tuple<tag<"age", int>, tag<"name", string>>;
template<typename T>
struct get_type_from_tag
{
using type = T;
};
template<const char *Str, typename T>
struct get_type_from_tag<tag<Str, T>
{
using type = T;
};
template<T>
using get_type_from_tag_t = typename get_type_from_tag<T>::type;
template<typename Tpl>
struct types_from_tuple {};
template<typename ...Ts>
struct types_from_tuple<named_tuple<Ts...>>
{
using type = type_list<get_type_from_tag_t<Ts>...>;
};
I think this case properly illustrates the syntax you designed, but could not prove the reasonableness of your idea, because there seems to be little motivation for a user to design the function template `func` defined above, and thus this case seems to be meaningless.Are you arguing that a demonstration is not an use-case? That's weak.
On Thursday, December 14, 2017 at 9:25:24 AM UTC-5, bastie...@gmail.com wrote:On Thursday, December 14, 2017 at 11:24:41 AM UTC+1, Mingxin Wang wrote:On Thursday, December 14, 2017 at 4:31:03 PM UTC+8, bastie...@gmail.com wrote2. I could not find a case that your idea could give full play to the advantages.I can give you one that's would be simple to use and useful : named_tuple.
named_tuple<int, "age", string, "name"> x;
x.get<"name">() = get_user_name();
static_assert(std::is_same_v<std::string &, decltype(std::get<1>())>);Which can be accomplished adequately enough in other ways (assuming we allow strings in template parameters):
named_tuple<tag<"age", int>, tag<"name", string>>;
Indeed, I think this would be a much better solution. First, it allows you to have named_tuples with unnamed elements (`named_tuple<int, tag<"name", string>>`). Second, I think it makes it easier on the implementation side. For example, if you want to write a metafunction that takes a `named_tuple<...>` and generates a list of its types, you can do this:
template<typename T>
struct get_type_from_tag
{
using type = T;
};
template<const char *Str, typename T>
struct get_type_from_tag<tag<Str, T>
{
using type = T;
};
template<T>
using get_type_from_tag_t = typename get_type_from_tag<T>::type;
template<typename Tpl>
struct types_from_tuple {};
template<typename ...Ts>
struct types_from_tuple<named_tuple<Ts...>>
{
using type = type_list<get_type_from_tag_t<Ts>...>;
};The metafunction for your code's equivalent would not be able to use expansion like that. Since every pair of `T` in the argument list contains the actual type, you would need to use recursion to extract the list of `T`s. Not the hardest thing in the world, but hardly as straightforward as this.
I think this case properly illustrates the syntax you designed, but could not prove the reasonableness of your idea, because there seems to be little motivation for a user to design the function template `func` defined above, and thus this case seems to be meaningless.Are you arguing that a demonstration is not an use-case? That's weak.What is so "weak" about it? An artificial example that doesn't have real-world utility is not an effective motivation for a feature. I admit that I'm not a deep metaprogramming expert, so it is entirely possible I'm missing something here. But thus far, I have not seen a genuinely compelling use case that cannot adequately be solved with some other mechanism.
This is an odd pattern that requires implementation knowledge on the part of the user.
On the implementation side I've worked on a non-recursive implementation that would rely on lambdas in unevaluated context (also opted in C++20) and that I believe could also allow optional names.That being said it is more difficult to write but library features are usually more difficult to write the more they wish to be easy to use.
Although your proposal is interesting, I don't see how it relates to this thread.This thread isn't about how we can encapsulate an type (if that is what you are getting at, which doesn't seem to be the case as you can't encapsulate templates in that manner). This is about being able to accept and manipulate any TPT, thus allowing more general transforms of any template. My tbind example above is a real world example of this.
On Saturday, December 16, 2017 at 11:01:22 PM UTC+8, adrian....@gmail.com wrote:Although your proposal is interesting, I don't see how it relates to this thread.This thread isn't about how we can encapsulate an type (if that is what you are getting at, which doesn't seem to be the case as you can't encapsulate templates in that manner). This is about being able to accept and manipulate any TPT, thus allowing more general transforms of any template. My tbind example above is a real world example of this.I am sorry that I did not get your idea at the beginning. If my understanding is correct this time, your intension is to popularize the term "overloading" to type templates.
// Template declaration
template <T>
struct X;
// Specialization 1
template <auto C>
struct X<C>
{
// C is a template constant
};
// Specialization 1a
template <typename T, T C>
struct X<C>
{
// C is a template constant (same as above)
// T is the type of the constant
// This specialization cannot exist in tandem with the one above
// (Specialization 1) as they are equivalent and would cause an ambiguity
// error.
};
// Specialization 2
template <typename T>
struct X<T>
{
// T is a typename
};
// Specialization 3
template <template <...> class TT>
struct X<TT>
{
// TT is a template that takes any number of UTPT.
};
// Specialization 3a
template <template <auto...> class TT>
struct X<TT>
{
// TT is a template that takes any number of constant TPTs.
};
// Specialization 3b
template <template <typename...> class TT>
struct X<TT>
{
// TT is a template that takes any number of typename TPTs.
};
// Specialization 3c
template <template <template <...> class...> class TT>
struct X<TT>
{
// TT is a template that takes any number of template TPTs that take any
// number of UTPTs.
};
// Specialization 3d
template <template <template <...> class, typename, int> class TT>
struct X<TT>
{
// TT is a template that takes a template that takes any number of UTPTs and
// a typename and an int constant.
};
However, some details might need to be taken into account before standardization.1. Is the motivation strong enough to define a new language feature?There is partial support of "template overloading" in the standard, and "overloading" is not inevitable. After all, any case that could be solved by overloading, could be easily handled with renaming.
2. If we have "template overloading", do we still need "template specialization"?"template specialization" is a widely used technique, and it seems that "template overloading" has some overlap with "template specialization". If there is enough motivation to keep both features for type templates, is it necessary to let function templates support "template specialization"? Otherwise, do we still need "template specialization"?
// Template declaration
template <template T>
struct X;
// Specialization 1
template <auto C>
struct X<C>
{
// C is a template constant
};
// Specialization 1a
template <typename T, T C>
struct X<C>
{
// C is a template constant (same as above)
// T is the type of the constant
// This specialization cannot exist in tandem with the one above
// (Specialization 1) as they are equivalent and would cause an ambiguity
// error.
};
// Specialization 2
template <typename T>
struct X<T>
{
// T is a typename
};
// Specialization 3
template <template <template...> class TT>
struct X<TT>
{
// TT is a template that takes any number of UTPT.
};
// Specialization 3a
template <template <auto...> class TT>
struct X<TT>
{
// TT is a template that takes any number of constant TPTs.
};
// Specialization 3b
template <template <typename...> class TT>
struct X<TT>
{
// TT is a template that takes any number of typename TPTs.
};
// Specialization 3c
template <template <template <template...> class...> class TT>
struct X<TT>
{
// TT is a template that takes any number of template TPTs that take any
// number of UTPTs.
};
// Specialization 3d
template <template <template <template...> class, typename, int> class TT>
struct X<TT>
{
// TT is a template that takes a template that takes any number of UTPTs and
// a typename and an int constant.
};
However, some details might need to be taken into account before standardization.1. Is the motivation strong enough to define a new language feature?There is partial support of "template overloading" in the standard, and "overloading" is not inevitable. After all, any case that could be solved by overloading, could be easily handled with renaming.We are not defining a new feature exactly, but extending an already existing one (template specialization) such that the template parameter type (TPT) can be unspecified.2. If we have "template overloading", do we still need "template specialization"?"template specialization" is a widely used technique, and it seems that "template overloading" has some overlap with "template specialization". If there is enough motivation to keep both features for type templates, is it necessary to let function templates support "template specialization"? Otherwise, do we still need "template specialization"?Again, this is template specialization not template overloading. If we attempted to add template overloading (if I understand your concept of it), it would require a lot of vetting as it would be a large departure from what the language supports now. What I am talking about is a tweak of the current system which is far less risky.
I think the motivation of this idea is not strong enough to a certain extent, because:1. Types, type templates and constexpr values usually have completely different semantics. Thus using this feature will inevitably carry ambiguation to the semantics of the templates. For example, if one is supposed to define some types like `X<int>` (type), `X<123>` (constexpr value) and `X<std::vector>` (type template), I do not agree that the three types shall have the same semantics, thus they shall have different names for their respective semantics, rather than sharing a same one.
2. Any case that could be solved by this feature, could also be easily handled with renaming.
3. Although you are "extending an already existing feature", it is still a core language feature that requires support from the compiler.
However, some details might need to be taken into account before standardization.1. Is the motivation strong enough to define a new language feature?There is partial support of "template overloading" in the standard, and "overloading" is not inevitable. After all, any case that could be solved by overloading, could be easily handled with renaming.We are not defining a new feature exactly, but extending an already existing one (template specialization) such that the template parameter type (TPT) can be unspecified.2. If we have "template overloading", do we still need "template specialization"?"template specialization" is a widely used technique, and it seems that "template overloading" has some overlap with "template specialization". If there is enough motivation to keep both features for type templates, is it necessary to let function templates support "template specialization"? Otherwise, do we still need "template specialization"?Again, this is template specialization not template overloading. If we attempted to add template overloading (if I understand your concept of it), it would require a lot of vetting as it would be a large departure from what the language supports now. What I am talking about is a tweak of the current system which is far less risky.Actually, I think "template overloading" might be more acceptible than the feature you propose; at least, it does not break the existing rules in declaring a template, and does not require new keywords (or reuse some existing keywords) with new semantics.
Enter code here...
using note = named_tuple<int, "year", "month", "day", double, "grade", std::string, "subject">;
using note = named_tuple<tag<int,"year","month","day">, tag<double, "grade">, tag<std::string,"subject">>;
The point of this idea is to increase expressivity for the library writer and simplify its utilisation by the user.As for the unevaluated lambdas, it is not required at all but they have the benefit to be inaccessible for the user (as opposed to tool classes/fn hidden in namespaces).My issue with tagged types is that it's a fix not a solution.It's a pattern where the user has to know a type and a tool type just to use it (two names required to express one idea).
I'm aware that it is used by a bunch of libraries and the reason why they do so is because there are no alternatives (or they are even worse for the user: decltype over overload function call, etc..).Going back the the generic template parameter idea, generally speaking this idea has the same goals, consequences, etc..., as the "auto non-type parameter" proposal had.On the named_tuple example, I believe that allowing a variable amount of strings after the type would make the tag implementation more difficult without really affecting the "template" one.