template <typename...Ts>
std::vector<size_t> foo(Ts&& ...) { return {#Ts...}; }
int main() {
std::vector a{0,1,2}, b=foo("hi", "ho", "hum");
assert(a == b); // succeeds
}
template <typename T>
struct StoredType { using type=T; };
/* various specializations of StoredType follow, but
* std::is_constructible<StoredType<T>::type, T> always holds
*/
template <typename... FTs>
struct Foo {
std::tuple<typename StoredType<FTs>::type...> _data;
Foo(FTs const & ...args) : _data{args...} { } // error!
};
Foo(FTs const & ...args) { init_data<0,FTs...>(args...); }
template <size_t M, typename T, typename... Rest>
void init_data(T const &arg, Rest const & ...rest) {
std::get<M>(_data) = arg;
init_data<M+1, Rest...>(rest...);
}
template <size_t M, typename T>
void init_data(T const &arg) {
std::get<M>(_data) = arg;
}
Foo(FTs const & ...args) {
auto x = {(std::get<#FTs>(_data)=args)...};
(void) x;
}
// workaround for fact that we can't make a std::initializer_list<void>
template <typename Action, typename T>
bool void_fold_one(Action &action, T &&t) {
action(std::forward<T>(t));
return true;
}
template <typename Action, typename T, typename... Ts>
void void_fold(Action &&action, T &&t, Ts&&... args)
{
auto x = {void_fold_one(action, std::forward<T>(t))...};
(void) x;
}
int main() {
auto action = [](auto x) { std::cout << x << " "; };
void_fold(action, 1, 3.14, "hi", std::endl);
}
template <size_t I, size_t N>
struct void_fold_tuple_ {
template <typename Action, typename Tuple>
static void call(Action &action, Tuple &tup)
{
action.template operator()<I>(tup);
using recursive = void_fold_tuple_<I+1, N>;
recursive::call(action, tup);
}
};
template <size_t N>
struct void_fold_tuple_<N, N> {
template <typename Action, typename Tuple>
static void call(Action&, Tuple&) { /* no-op */ }
};
struct print_action {
template <typename Tuple, size_t I>
void operator()(Tuple &tup) {
using std::get; // ADL
std::cout << std::get<I>(tup) << " ";
}
};
template <typename Action, typename Tuple>
void void_fold_tuple(Action &&action, Tuple &&tup)
{
using std::tuple_size; // ADL
using tuple = typename std::remove_reference<Tuple>::type;
using recursive = void_fold_tuple_<0, tuple_size<tuple>::value>;
recursive::call(action, tup);
}
int main() {
std::tuple<int, double, char const *> tup{1,3.14,"hi",std::endl};
void_fold_tuple(print_action{}, tup);
};
template <typename Action, template <typename ...> class Tuple, typename ...FTs>
void void_fold_tuple(Action &&action, Tuple<FTs...> &&tup)
{
auto x = {void_fold_one(action, std::get<#FTs>(tup)...)};
(void) x;
}
int main() {
std::tuple<int, double, char const *> tup{1, 3.14, "hi", std::endl};
void_fold_tuple(print_action{}, tup);
};
template<int...>
struct Sequence { };
template<size_t N, size_t
... S>
struct GenSequence {
using type = typename GenSequence<N-1, N-1, S...>::type;
};
template<size_t
... S>
struct GenSequence<0, S...>
{
using type = Sequence<S...>;
};
template <typename... Ts>
struct Foo {
using Tuple = std::tuple<Ts* ...>;
using Vector = std::vector<void*>;
using Seq = typename GenSequence<sizeof...(Ts)>::type;
Foo(Vector const &v) {
init_pointers(v, Seq());
}
template <size_t ... S>
void init_pointers(Vector const &v, Seq) {
_pointers = Tuple{dynamic_cast<Ts*>(v[S])...};
}
Tuple _pointers;
};
template <typename... Ts>
struct Foo {
using Tuple = std::tuple<Ts* ...>;
using Vector = std::vector<void*>;
Foo(Vector const &v)
: _pointers{dynamic_cast<Ts*>(v[#Ts])...}
{
}
Tuple _pointers;
};
template <size_t M, typename T, typename... Ts>
struct Select{
using type = typename Select<M - 1, Ts...>::type;
};
template <typename T, typename... Ts>
struct Select<0, T, Ts...>{
using type = T;
};
template <template <typename...> class Tuple, typename... Ts>
auto reverse(Tuple<Ts...> const &tup) {
static constexpr size_t const N = sizeof...(Ts) - 1;
using RTuple = Tuple<typename Select<N - #Ts, Ts...>::type...>;
return RTuple<{std::get<N-#Ts>(tup)...};
}
template <typename...Ts>
std::vector<size_t> foo(Ts&& ...) { return {std::index_sequence_for<Ts...>::value...}; }
template<class T, T... Ints>
struct integer_sequence {
using value_type = T;
using value = <Ints...>; // new typelist member after n3728; syntax TBD
static constexpr std::size_t size() { return sizeof...(Ints); }
};
template <typename... Ts>
struct index_sequence_for {
using value = <#Ts...>;
};
On 2015–07–21, at 2:08 AM, sco...@gmail.com wrote:The idea I'd like to float, then, is that the position of a given type within its pack should be accessible during unpacking:template <typename...Ts>
std::vector<size_t> foo(Ts&& ...) { return {#Ts...}; }
template <typename T>
int hash(T const &t);
/* ^^^ to be specialized for lots of
types */
template <template <typename...> class Tuple, typename... T>
int hash(Tuple<T...> const &tup)
{
using
std::get; //
ADL
return (... ^ hash(get<__indexof(T)>(tup)));
}
--
---
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/u9XLfRCKzGc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
How about a special alias template, like this:
template< typename type, std::size_t index >using indexed_type = type; // Compiler magic deduces index, too
template <typename ... Ts, std::size_t ... Xs>
std::vector<std::size_t> foo( std::indexed_type< Ts, Xs > && ...) { return {Xs...}; }
An alias template can produce a deduced context as its result. Rather than introducing a new operator syntax for pack indexes, the library can encapsulate the “operator.” The standard only needs to say that index arguments are a deduced context when indexed_type is used in a pack pattern.
template <typename T, size_t N=/*magic*/> struct indexof { using type = T; /* for completeness */ static constexpr size_t value = N; };I'm not sure I follow how foo in your example would be used, tho? That code would probably not compile, at least not if you invoked:
foo(1, 3.14, "hi")Type deduction machinery generally fails if the types in the function's argument list are derived somehow from---rather than directly using---the types in the functions template parameter list.
template <typename T, size_t N=0> struct indexof { using type = T; /* for completeness */ static constexpr size_t value = N; }; template <typename... T> std::vector<size_t> foo(T && ...args) { return {std::indexof<T>::value...}; }Versus a new keyword like __indexof, which operates in an unevaluated context like alignof() and sizeof(), a magic std::indexof template has the definite advantages of not polluting the global namespace. A slight advantage is that std::indexof could degrade gracefully to value=0 when passed a normal (non-pack) template parameter where __indexof would fail to compile---though I'm not sure that's important, given the intended use.
How about a special alias template, like this:
template< typename type, std::size_t index >using indexed_type = type; // Compiler magic deduces index, too
template <typename ... Ts, std::size_t ... Xs>
std::vector<std::size_t> foo( std::indexed_type< Ts, Xs > && ...) { return {Xs...}; }
An alias template can produce a deduced context as its result. Rather than introducing a new operator syntax for pack indexes, the library can encapsulate the “operator.” The standard only needs to say that index arguments are a deduced context when indexed_type is used in a pack pattern.
On 22/07/2015 2:14 AM, David Krauss wrote:
How about a special alias template, like this:
template< typename type, std::size_t index >using indexed_type = type; // Compiler magic deduces index, too
template <typename ... Ts, std::size_t ... Xs>
std::vector<std::size_t> foo( std::indexed_type< Ts, Xs > && ...) { return {Xs...}; }
An alias template can produce a deduced context as its result. Rather than introducing a new operator syntax for pack indexes, the library can encapsulate the “operator.” The standard only needs to say that index arguments are a deduced context when indexed_type is used in a pack pattern.
Compiler magic can be a good thing... and the committee seems to favor it as well, with all those C++11 type traits being a prime example.
If I understand correctly, perhaps this might be another way of stating your idea?
template <typename T, size_t N=/*magic*/> struct indexof { using type = T; /* for completeness */ static constexpr size_t value = N; };
template
<typename T>
struct
is_trivially_destructible {
static constexpr bool value = __is_trivially_destructible(T);
};
template
<typename T, size_t N=__indexof(T)>
struct indexof {
static
constexpr
size_t value =
N;
};
template
<typename T, size_t N=__indexof(T)> struct indexof;
template
<int...
T,
size_t N=__indexof(T)> struct indexof;
template
<template <typename...> class T, size_t N=__indexof(T)> struct indexof;
Thoughts?
Ryan
return { std::indexof<Ts>... };
And this magically works whether Ts is a pack of types, values or templates.
On 2015–07–23, at 7:39 AM, Ryan Johnson <sco...@gmail.com> wrote:
Unfortunately, the obvious analogue for std::indexof<T> would be:
template <typename T, size_t N=__indexof(T)>
struct indexof {
static constexpr size_t value = N;
};
- More worrisome, the std::indexof<T> approach cannot support non-type template parameters:
- Thus, you'd need three different names for the std::indexof<T> concept, one for each kind of parameter pack, which would be pretty annoying. The __indexof() built-in faces no such difficulty.
[...]
- Thus, you'd need three different names for the std::indexof<T> concept, one for each kind of parameter pack, which would be pretty annoying. The __indexof() built-in faces no such difficulty.
Maybe Clang can implement __indexof() without difficulty, but an operator that equally accepts a template name, type, or expression as an argument doesn’t fit into the language grammar (and might not fit into other implementations) as easily.
On 2015–07–23, at 7:39 AM, Ryan Johnson <sco...@gmail.com> wrote:
Unfortunately, the obvious analogue for std::indexof<T> would be:
template <typename T, size_t N=__indexof(T)>
struct indexof {
static constexpr size_t value = N;
};
That’s why I suggested an alias template. Alias template substitutions occur before argument substitutions or type deduction. So, the usagetemplate <typename ... Ts, std::size_t ... Xs>
std::vector<std::size_t> foo( std::indexed_type< Ts, Xs > && ...) { return {Xs...}; }
gets transformed into something like
template <typename ... Ts, std::size_t ... Xs>
std::vector<std::size_t> foo( Ts [[__index (Xs)]] && ...) { return {Xs...}; }
What I’m trying to illustrate here is that the parameter type will be Ts && regardless of the Xs values, but Xs can still be deduced from the context where the alias template was used, because the alias template has been substituted away before deduction begins.
- Thus, you'd need three different names for the std::indexof<T> concept, one for each kind of parameter pack, which would be pretty annoying. The __indexof() built-in faces no such difficulty.
...
Personally, I consider non-type template parameters as second-class citizens and keep them quarantined in the smallest possible utility classes.
template <typename
T>
using
my_indexof =
std::indexof<T>; // why not?
template
<size_t
N>
using int_indexof = std::indexof<N>; // eh???
namespace foo { template <typename T> size_t indexof = /* something */; }
namespace bar { template <int N> size_t indexof = /* something */; }
using std::indexof;
using foo::indexof;
using bar::indexof;
/* what now? */
On Thursday, July 23, 2015 at 7:53:00 AM UTC+1, David Krauss wrote:
[...]
- Thus, you'd need three different names for the std::indexof<T> concept, one for each kind of parameter pack, which would be pretty annoying. The __indexof() built-in faces no such difficulty.
Maybe Clang can implement __indexof() without difficulty, but an operator that equally accepts a template name, type, or expression as an argument doesn’t fit into the language grammar (and might not fit into other implementations) as easily.
don't know about template name, but sizeof has accepted either type or an expression without issues since forever.
Also new operators have been added recently (decltype, alignas, noexcept), without the need to wrap them in a metafunction veneer. Ryan should just propose the __indexof operator as implemented and leave the non-clashing-name bikeshedding to the committee.
template <typename
T,
size_t N=__indexof(T)>
constexpr
size_t const
indexof = N;
template <typename... Ts>
struct
indexes {
static
constexpr
size_t values[]
=
{std::indexof<Ts>...};
};
On 2015–07–23, at 7:59 PM, Ryan Johnson <sco...@gmail.com> wrote:On 23/07/2015 12:52 AM, David Krauss wrote:
OK, I read that too quickly before and didn't "get" that std::indexed_type<Ts,Xs> *is* Ts.
That’s why I suggested an alias template. Alias template substitutions occur before argument substitutions or type deduction. So, the usagetemplate <typename ... Ts, std::size_t ... Xs>
std::vector<std::size_t> foo( std::indexed_type< Ts, Xs > && ...) { return {Xs...}; }
gets transformed into something like
template <typename ... Ts, std::size_t ... Xs>
std::vector<std::size_t> foo( Ts [[__index (Xs)]] && ...) { return {Xs...}; }
What I’m trying to illustrate here is that the parameter type will be Ts && regardless of the Xs values, but Xs can still be deduced from the context where the alias template was used, because the alias template has been substituted away before deduction begins.
However, the compiler won't know that, and I'm still struggling to see how the code you propose could actually compile. I'm not aware of any case where today's compilers can "work backward" from a function arg whose type is both instantiation-dependent and type-dependent on T, and successfully infer what T should be (unless T is also used directly).
Also, even if it can somehow be made to compile, that style of syntax is very different from today's C++, which would worry me. The committee seems to be less enthusiastic about proposals that require novel new syntax in order to work.
Actually, that's a good point, and the whole point of this proposal is drastically reduce the need for `template <typename... Ts> template <sizeof_t... Ns>' nesting that's often required for working with std::tuple.
Template template parameters are handy in the cases where they're useful---I think template <template <typename...> class Tuple, typename... Ts> is the official way to work with tuple-like objects, for example---but those are definitely less common than type parameters.
Anyway, I guess the world would not end if there were a std::indexof for typed, std::indexofN for non-type, and std::indexofT for template template or some such. Still ugly, though.
On 2015–07–23, at 7:59 PM, Ryan Johnson <sco...@gmail.com> wrote:
On 23/07/2015 12:52 AM, David Krauss wrote:
OK, I read that too quickly before and didn't "get" that std::indexed_type<Ts,Xs> *is* Ts.
That’s why I suggested an alias template. Alias template substitutions occur before argument substitutions or type deduction. So, the usagetemplate <typename ... Ts, std::size_t ... Xs>
std::vector<std::size_t> foo( std::indexed_type< Ts, Xs > && ...) { return {Xs...}; }
gets transformed into something like
template <typename ... Ts, std::size_t ... Xs>
std::vector<std::size_t> foo( Ts [[__index (Xs)]] && ...) { return {Xs...}; }
What I’m trying to illustrate here is that the parameter type will be Ts && regardless of the Xs values, but Xs can still be deduced from the context where the alias template was used, because the alias template has been substituted away before deduction begins.
However, the compiler won't know that, and I'm still struggling to see how the code you propose could actually compile. I'm not aware of any case where today's compilers can "work backward" from a function arg whose type is both instantiation-dependent and type-dependent on T, and successfully infer what T should be (unless T is also used directly).
Since C++11, this will compile:
template< typename t >using same_type = t;
template< typename t >t id( same_type< t > value ) { return value; }
int q = id( 3 );
same_type is an alias template, hence it is substituted before deduction. There’s no working backward.
My suggestion is to add a hidden deduced context, e.g. in an internal attribute. That’s what the compiler would know about.
Actually, that's a good point, and the whole point of this proposal is drastically reduce the need for `template <typename... Ts> template <sizeof_t... Ns>' nesting that's often required for working with std::tuple.
Can you cite some code with this symptom? The problem doesn’t look familiar to me.
Anyway, I guess the world would not end if there were a std::indexof for typed, std::indexofN for non-type, and std::indexofT for template template or some such. Still ugly, though.
At some point, you start to rationalize dropping the std:: and making it a first-class operator. I think the most intuitive behavior, if operators are allowed, is to have something that expands to an index sequence pack of a given length — no enclosing pack pattern needed.
template <typename...Ts>
std::vector<size_t> foo(Ts&& ...) {constexpr std::size_t pack_length = sizeof ... (Ts);return { index_sequence( pack_length ) ... };}
This would allow users to generate expressions from patterns without introducing templates at all.
On 2015–07–23, at 9:27 PM, Ryan Johnson <sco...@gmail.com> wrote:On 23/07/2015 6:48 AM, David Krauss wrote:
The part that worries me is this code does not compile:
On 2015–07–23, at 7:59 PM, Ryan Johnson <sco...@gmail.com> wrote:
On 23/07/2015 12:52 AM, David Krauss wrote:
OK, I read that too quickly before and didn't "get" that std::indexed_type<Ts,Xs> *is* Ts.
That’s why I suggested an alias template. Alias template substitutions occur before argument substitutions or type deduction. So, the usagetemplate <typename ... Ts, std::size_t ... Xs>
std::vector<std::size_t> foo( std::indexed_type< Ts, Xs > && ...) { return {Xs...}; }
gets transformed into something like
template <typename ... Ts, std::size_t ... Xs>
std::vector<std::size_t> foo( Ts [[__index (Xs)]] && ...) { return {Xs...}; }
What I’m trying to illustrate here is that the parameter type will be Ts && regardless of the Xs values, but Xs can still be deduced from the context where the alias template was used, because the alias template has been substituted away before deduction begins.
However, the compiler won't know that, and I'm still struggling to see how the code you propose could actually compile. I'm not aware of any case where today's compilers can "work backward" from a function arg whose type is both instantiation-dependent and type-dependent on T, and successfully infer what T should be (unless T is also used directly).
Since C++11, this will compile:
template< typename t >using same_type = t;
template< typename t >t id( same_type< t > value ) { return value; }
int q = id( 3 );
same_type is an alias template, hence it is substituted before deduction. There’s no working backward.
My suggestion is to add a hidden deduced context, e.g. in an internal attribute. That’s what the compiler would know about.
template <typename T, size_t N=0>
using indexed_type = T;
template <typename T, typename N>
size_t foo(indexed_type<T,N> &&) { return N; }
int main() { return foo("hi"); }
Because the compiler cannot figure out how to come up with N while instantiating foo. On gcc-4.9 for example:
I fail to see how adding magic to indexed_type, so that N takes a meaningful value, would get past the deduction failure that has already occurred before the compiler ever tried to instantiate indexed_type.
Today's compiler can't work backwards from the magical N of indexed_type in order to deduce the ordinary N of foo; type aliases do not change that situation as far as I can tell---your example compiles because the user has already supplied T and so alias substitution can go ahead; the user has *not* supplied N and so the compiler is unable to continue.
That's the part I was hoping you could elaborate on.
The code examples I cited in my original message can all be cast to use integer sequences instead of recursion; we chose to go with recursion rather than a variadic template helper function that takes a std::integer_sequence as an argument (following advice that variadic templates are slow and should be avoided when possible).Actually, that's a good point, and the whole point of this proposal is drastically reduce the need for `template <typename... Ts> template <sizeof_t... Ns>' nesting that's often required for working with std::tuple.
Can you cite some code with this symptom? The problem doesn’t look familiar to me.
The documentation for std::integer_sequence has some nice examples:
http://en.cppreference.com/w/cpp/utility/integer_sequence
Stack Overflow is crawling with questions on the theme of how to turn <typename ...Ts> into <size_t ...Ns> when dealing with tuples. Here are just a few that turned up when I searched for "c++11 index tuple”
template <typename... Ts>
int hash(std::tuple<Ts...> const &tup) {
return helper<Ts...>::hash(tup, std::integer_sequence_for<Ts...>());
}
With __indexof() the body of the hash function for std::tuple reduces to:
return (... ^ hash(std::get<__indexof(Ts)>(tup)));
Seems like, if you're going to go this route, you'd want something like the N3723 someone mentioned earlier in this thread. That's a vastly bigger and more invasive feature than what I was proposing here, tho. Plus, I get the sense that the two are orthogonal: you could still benefit from an __indexof() as a simpler alternative to marshalling all that parameter pack manipulation machinery N3723 proposes.
An unpack_sequence operator would be self-contained. N3728 is about dropping std::tuple from std::tuple< a, b, c > such that <a, b, c> is its own entity. There’s no particular connection.
template < typename tuple_like >
int hash(tuple_like const &tup) {
return ... ^ hash( std::get< std::make_index_sequence_v< std::tuple_size< tuple_like >::value > >( tup ) );
} // ^-- metafunction returning typelist 0..N-1
using indexed_type = T;
template <typename T, typename N>
size_t foo(indexed_type<T,N> &&) { return N; }
int main() { return foo("hi"); }
Because the compiler cannot figure out how to come up with N while instantiating foo. On gcc-4.9 for example:
Yes, that is the hidden magic. It’s not a pure library solution. It’s a library interface with no need to add new syntax.
I fail to see how adding magic to indexed_type, so that N takes a meaningful value, would get past the deduction failure that has already occurred before the compiler ever tried to instantiate indexed_type.
Today's compiler can't work backwards from the magical N of indexed_type in order to deduce the ordinary N of foo; type aliases do not change that situation as far as I can tell---your example compiles because the user has already supplied T and so alias substitution can go ahead; the user has *not* supplied N and so the compiler is unable to continue.
The user doesn’t supply N, deduction (the “compiler magic”) does. I illustrated it as an attribute:
template <typename ... Ts, std::size_t ... Xs>
std::vector<std::size_t> foo( Ts [[__index (Xs)]] && ...) { return {Xs...}; }
__index(Xs) is the deduced context. The double brackets are unnecessary, serving only for illustrative syntactic orientation. For comparison, consider this:
template< typename t, std::size_t v >
using ic = std::integral_constant< t, v >;
template <typename ... Ts, std::size_t ... Xs>
std::vector<std::size_t> foo( ic< Ts, Xs > ...) { return {Xs...}; }
int main() {auto v = foo( std::integral_constant< std::size_t, 3 >{}, std::integral_constant< std::size_t, 42 >{} );
}
This is valid C++11. The compiler doesn’t need the arguments of template parameters in order to substitute them into an alias template. (Default template arguments of alias templates don’t work the same way, so you might get confused if you tend to add default arguments everywhere. I never suggested any default arguments.)
The code examples I cited in my original message can all be cast to use integer sequences instead of recursion; we chose to go with recursion rather than a variadic template helper function that takes a std::integer_sequence as an argument (following advice that variadic templates are slow and should be avoided when possible).Actually, that's a good point, and the whole point of this proposal is drastically reduce the need for `template <typename... Ts> template <sizeof_t... Ns>' nesting that's often required for working with std::tuple.
Can you cite some code with this symptom? The problem doesn’t look familiar to me.
Huh? std::tuple_element is not slower than a recursive template that reinvents it. Parameter packs are usually faster than recursive templates. Recursive templates are the C++98-era solution.
The GenSequence example makes it look like you were unaware of the existence of std::make_index_sequence.
The documentation for std::integer_sequence has some nice examples:
http://en.cppreference.com/w/cpp/utility/integer_sequence
Stack Overflow is crawling with questions on the theme of how to turn <typename ...Ts> into <size_t ...Ns> when dealing with tuples. Here are just a few that turned up when I searched for "c++11 index tuple”
I see. So "`template <typename... Ts> template <sizeof_t... Ns>’ nesting” is referring to the practice of declaring a nested class template, and then defining it out side the enclosing class definition. But why would someone write it that way?
I don’t see the objectionable syntax anywhere in [the StackOverflow links]. Perhaps you could be more specific.
template <typename... Ts>
int hash(std::tuple<Ts...> const &tup) {
return helper<Ts...>::hash(tup, std::integer_sequence_for<Ts...>());
}
You still didn’t use any "template <typename... Ts> template <sizeof_t... Ns> nesting,” at least not per se.
The nesting is here:
template < typename tuple_like >
int hash(tuple_like const &tup) {
return hash( ... ^ std::get< unpack_sequence( std::tuple_size< tuple_like >::value ) >( tup ) );
}
On 2015–07–25, at 4:33 AM, Ryan Johnson <sco...@gmail.com> wrote:On 23/07/2015 9:06 PM, David Krauss wrote:
A stand-in for the missing compiler magic. Makes N available without the user having to specify it directly. Though I start to get the impression you want the magic to happen directly in the signature/declaration of foo, triggered by the compiler's recognizing the (otherwise utterly mundane) std::indexed_type?
What’s the default argument for?
If I understand correctly, you are proposing to make foo() itself magical, with the magic triggered by the appearance of an otherwise mundane std::indexed_type in its signature, or by some variable attribute affixed to the args. The problem is, the way template deduction works in C++, the compiler will not even consider foo() because the Xs cannot be deduced from information supplied *at the call site* and so it will never see the bits that are supposed to trigger the magic that would provide Xs.
Changing that behavior would be a Big Deal, because it changes the semantics of template selection---which would already be a big deal by itself---in a way that requires the compiler to do significantly more work for all template candidates (searching for bits of magic in their signatures), when the vast majority of them will not actually make use of the feature. I doubt the Committee would be interested in making such a large change to the language to support such a small feature.
That said, it's true that we're slowly moving away from recursion as the be-all/end-all. C++11 variadic templates start to address some reasons we use recursion so much, as do its restricted constexpr functions. C++14 std::index_sequence helps a bit (by at least hiding some kinds of recursion in a library class), and its generalized constexpr functions-help a lot (by moving more complex meta-computations to function rather than template code); C++17 fold expressions are another big step (by eliminating recursion entirely from compatible kinds of computations). Most of those approaches don't eliminate the explosion of template helper functions, though (fold expressions being a massive exception to that claim).
This proposal is just trying to eliminate a source of recursion and/or helper functions that these previous advances don't seem to have covered. The main messiness that would be left is std::tuple and std::get, both of which seem pretty difficult to achieve non-recursively.
template < typename tuple_like >
int hash(tuple_like const &tup) {
return hash( ... ^ std::get< unpack_sequence( std::tuple_size< tuple_like >::value ) >( tup ) );
}
In contrast, your unpack_sequence would have to "hoist" an otherwise non-pack statement, giving it a second set of variadic templates (the size_t... sequence).
N3728 would give something nearly identical. Both create a new parameter pack to be unpacked, in addition to any packs that were already present.
On the other hand, these "create-a-pack" approaches have the benefit of *not* needing to explicitly bring in a <typename...> pack. That could be useful at times, such as the tuple_like idiom you make a good argument for using. Creating a pack also means the code won't care whether there are other packs around; nor would it care whether those other packs are type, non-type, or template template.
On 2015–07–25, at 10:09 AM, David Krauss <pot...@gmail.com> wrote:The magic would happen in a modification to the type T generated by indexed_type<T>. In terms of the [[__index]] attribute:// Standard library source codetemplate< typename type >using indexed_type = Ts [[__index (Xs)]];
void test_function(const std::tuple<int, int, int> &, const std::tuple<int, int> &, const std::tuple<int> &)
{
std::puts("meow");
}
template <typename... Ts>
void intermediate_function()
{
test_function(std::tuple<Ts, restof...(Ts)>()...);
}
void caller_function()
{
intermediate_function<int, int, int>();
}
template <typename...Ts>
std::vector<std::size_t> foo(Ts&& ...) { return {sizeof...(Ts) - sizeof...(restof...(Ts)) - 1}; }
int main() {
std::vector a{0,1,2}, b=foo("hi", "ho", "hum");
assert(a == b); // succeeds
}
What if there were a keyword "restof...(Xs)" (not real name) that returned a template parameter pack of everything after the currently-expanding one?
This would work and even be more flexible than an "indexof...(Xs)" operator.
void test_function(const std::tuple<int, int, int> &, const std::tuple<int, int> &, const std::tuple<int> &)
{
std::puts("meow");
}
template <typename... Ts>
void intermediate_function()
{
test_function(std::tuple<Ts, restof...(Ts)>()...);
}
void caller_function()
{
intermediate_function<int, int, int>();
}
Then the example from the beginning of the thread becomes:
template <typename...Ts>
std::vector<std::size_t> foo(Ts&& ...) { return {sizeof...(Ts) - sizeof...(restof...(Ts)) - 1}; }
Something is off with this example; a missing "..." perhaps?
template <typename...Ts>
std::vector<std::size_t> foo(Ts&& ...)
{
return {(sizeof...(Ts) - sizeof...(restof...(Ts)) - 1)...};
}
On 2015–07–25, at 4:33 AM, Ryan Johnson <sco...@gmail.com> wrote:
On 23/07/2015 9:06 PM, David Krauss wrote:
A stand-in for the missing compiler magic. Makes N available without the user having to specify it directly. Though I start to get the impression you want the magic to happen directly in the signature/declaration of foo, triggered by the compiler's recognizing the (otherwise utterly mundane) std::indexed_type?
What’s the default argument for?
The magic would happen in a modification to the type T generated by indexed_type<T>. In terms of the [[__index]] attribute:
// Standard library source codetemplate< typename type >using indexed_type = Ts [[__index (Xs)]];
// User source codetemplate <typename ... Ts, std::size_t ... Xs>
std::vector<std::size_t> foo( std::indexed_type< Ts, Xs > && ...) { return {Xs...}; }
// After alias template substitutiontemplate <typename ... Ts, std::size_t ... Xs>
std::vector<std::size_t> foo( Ts [[__index (Xs)]] && ...) { return {Xs...}; }
// ^ both deduced ^
The implementation work would be in adding a new deduced context. (Again, the implementation would not likely use actual double-bracket attribute syntax, I’m just showing where such an internal keyword could fit in the grammar.)
You could say that std::indexed_type would generate a magic signature, but only by applying ordinary alias template substitution to a magic keyword.
If I understand correctly, you are proposing to make foo() itself magical, with the magic triggered by the appearance of an otherwise mundane std::indexed_type in its signature, or by some variable attribute affixed to the args. The problem is, the way template deduction works in C++, the compiler will not even consider foo() because the Xs cannot be deduced from information supplied *at the call site* and so it will never see the bits that are supposed to trigger the magic that would provide Xs.
Parameters are deduced when they are mentioned in deduced contexts. If the compiler’s internals need an argument to correspond to the index sequence parameter, it can simply generate such a pack, upon encountering the magic keyword in the signature.
Changing that behavior would be a Big Deal, because it changes the semantics of template selection---which would already be a big deal by itself---in a way that requires the compiler to do significantly more work for all template candidates (searching for bits of magic in their signatures), when the vast majority of them will not actually make use of the feature. I doubt the Committee would be interested in making such a large change to the language to support such a small feature.
We can’t prove the implementation difficulty without trying to implement it. The proof (or disproof) is in the pudding.
This proposal is just trying to eliminate a source of recursion and/or helper functions that these previous advances don't seem to have covered. The main messiness that would be left is std::tuple and std::get, both of which seem pretty difficult to achieve non-recursively.
The proposal doesn’t eliminate any template recursion, it eliminates [make_]index_sequence uses and attendant helper template declarations.
template < typename tuple_like >
int hash(tuple_like const &tup) {
return hash( ... ^ std::get< unpack_sequence( std::tuple_size< tuple_like >::value ) >( tup ) );
}
In contrast, your unpack_sequence would have to "hoist" an otherwise non-pack statement, giving it a second set of variadic templates (the size_t... sequence).
unpack_sequence(N) would simply be an unexpanded pack (§14.5.3/6). “Hoisting” is just what happens whenever you name a pack. The possibility of an unexpanded pack in a non-template context is also part of the price of N3728.
N3728 would give something nearly identical. Both create a new parameter pack to be unpacked, in addition to any packs that were already present.
On the other hand, these "create-a-pack" approaches have the benefit of *not* needing to explicitly bring in a <typename...> pack. That could be useful at times, such as the tuple_like idiom you make a good argument for using. Creating a pack also means the code won't care whether there are other packs around; nor would it care whether those other packs are type, non-type, or template template.
Right.
I see this as a dichotomy between two local optimums: If no new keyword is allowed, the best solution is indexed_type because it doesn’t care whether the pack is type or non-type, it only requires a type in a deduced pack pattern.
If a new keyword is allowed, the best solution is unpack_sequence because it allows you to locally unpack things (even numeric sequences with other pack basis), without introducing any new kind of entity to the language.
On 2015–07–25, at 11:18 PM, Ryan Johnson <sco...@gmail.com> wrote:However... I just realized there's a glaring problem with this approach: it does not work with member functions of a template class. To give a simplistic example:
template <typename... Ts>
struct my_tuple : std::tuple<Ts...>
{
using std::tuple<Ts...>::tuple;
int hash()
{
/* Nothing to attach [[__index(Xs)]] to, nor std::indexed_type */
}
};
There's no clear way to make my_tuple::hash() a template function in a way that allowed the necessary Ts,Xs pairing, because doing so would create a new/different typed parameter pack and hash() doesn't take any arguments to kick-start deduction with. It also wouldn't work to require the Xs... at class level, because that would force the user to supply all the Xs when instantiating the class. You couldn't hide the extra template parameters behind an alias template, because those suffer the same shortcoming.
The __indexof operator doesn't suffer this problem because it can work directly with the existing parameter pack, rather than having to manufacture its own "special" one.
Based on that, I'd say both the std::indexed_type<Ts,Xs> and Ts [[__index(Xs)]] ideas are out of the running.
I think I know enough that I could attempt a prototype of the Ts [[pack_index(Xs)]] attribute approach to see whether it's easy or even possible to generate the extra parameter Xs parameter pack at the right moment.
The std::indexed_type<Ts,Xs> flavor of magic would be a non-starter, though, because there is no machinery in place to detect a "special" template class name (which could be obfuscated arbitrarily by use of typedefs and template aliases).
Using std::make_index_sequence just moves the recursion from the call site into library code, which changes nothing from the compiler's perspective (see below). That's what I meant when I described it as "hiding some kinds of recursion in a library class."This proposal is just trying to eliminate a source of recursion and/or helper functions that these previous advances don't seem to have covered. The main messiness that would be left is std::tuple and std::get, both of which seem pretty difficult to achieve non-recursively.
The proposal doesn’t eliminate any template recursion, it eliminates [make_]index_sequence uses and attendant helper template declarations.
By "hoisting" I meant the use of an unexpanded parameter pack in a context where one is not otherwise expected (thank you for helping me nail that down with your concise statement). IMO that capability is the main feature of N3728. Without the first-class parameter packs that N3728 proposes, the notion of an unexpanded parameter pack outside any variadic template context---or inside a variadic template context, but in an expression that does not mention any of the formal parameter packs---is completely foreign.
BTW, even if something like N3728 were accepted, it does not provide a clean mechanism for converting a parameter pack into a <size_t...> pack, so this proposal would still be useful. It does close an important gap, though, by providing a way to define std::tuple non-recursively.
I'm not seeing how you could use indexed_type with non-type or template template parameter packs. If you still want to pursue it in spite of the flaws discussed earlier, could you provide an example?
Eh? It does require a new entity: an unexpanded parameter pack that is not part of any enclosing variadic template definition.If a new keyword is allowed, the best solution is unpack_sequence because it allows you to locally unpack things (even numeric sequences with other pack basis), without introducing any new kind of entity to the language.
While it might be tempting to hack it in quietly for this one (very limited) use case, I suspect the Committee would much rather that any new entity be fleshed out fully to avoid surprises and ugliness later on, when people inevitably try to apply the concept in new and unanticipated ways.
That said, the unpack_sequence idea *is* tempting because it lets you work with subsequences, an ability that __indexof() operator would not provide. But I also feel like ability to slice and dice parameter packs would be better integrated with N3728 or proposed as a separate feature (which would fill the one remaining gap when working with tuples, namely how to implement things like std::get and std::tuple_element non-recursively).
On 2015–07–27, at 6:52 AM, Edward Catmur <e...@catmur.co.uk> wrote:std::get is already nonrecursive in any modern implementation using variadic inheritance. Admittedly, you still need recursion to generate the index_sequence - but as you pointed out above, that gets memoized for any N and smaller.
The return type of std::get is usually determined by std::tuple_element, so my assertion is that a nonrecursive tuple_element implies a nonrecursive get.It’s also possible to eliminate the tuple_element metafunction by replacing it with C++14 deduced return type. That would require the tuple base classes (one per element) to have types like __tuple_base< tuple< element ... >, index > whereas current implementations use something like __tuple_base< element, index >.
template<std::size_t I, class T> T& tuple_get(tuple_base<I, T>& obj) { return obj.value; }
template<bool, class> struct nth_type_base {};
template<class T> struct nth_type_base<true, T> { using type = T; };
template<std::size_t I, class...> struct nth_type_impl;
template<std::size_t I, std::size_t... Is, class... Ts>
struct nth_type_impl<I, std::index_sequence<Is...>, Ts...> : nth_type_base<I == Is, Ts>... {};
template<std::size_t I, class T> struct tuple_element;
template<std::size_t I, class... Ts>
struct tuple_element<I, tuple<Ts...>> : nth_type_impl<I, std::index_sequence_for<Ts...>, Ts...> {};
If it’s really such a win to eliminate template recursion from std::get, implementations already have various means to do so.
On 2015–07–27, at 4:23 PM, Edward Catmur <e...@catmur.co.uk> wrote:Actually, you can have nonrecursive get without having to put all the elements into tuple_base. I find the most natural implementation is the following:template<std::size_t I, class T> T& tuple_get(tuple_base<I, T>& obj) { return obj.value; }
If you call tuple_get<N> on the variadically derived class then everything gets sorted out by overload resolution on the implicit object parameter.
Similarly, there's a nice trick using variadic inheritance to compute tuple_element (and/or nth_type) without recursion or any magic:
If it’s really such a win to eliminate template recursion from std::get, implementations already have various means to do so.Definitely - and as you say, it's surprising that they haven't yet.
On 2015–07–27, at 8:40 PM, Ryan Johnson <sco...@gmail.com> wrote:That's very clever. The recursion is still there---hiding in amalgam_ftor's abuse of std::common_type ---but at least it only has to happen once when a instantiating each kind of tuple that way.
FYI, the code fails to compile with gcc-4.9.
First problem is minor: struct amalgam has no default constructor (needed when playing with the type_map decltype stuff). Solution is to either add "amalgam() = default" to the amalgam class definition, or to use std::declval<> inside the decltype() to obtain an amalgam instance.
Second problem is weird: "error: request for member ‘tuple_entry’ is ambiguous" (naming tuple_entry<2>, tuple_entry<1>, and tuple_entry<0> as candidates). Compiles fine with clang-3.6, so I don't know whether it's a gcc bug or if your code relies on a clang-ism. I suspect the former since I see no reason why asking for tuple_entry<1> should consider tuple_entry<0> or tuple_entry<2> at all, let alone consider them to be ambiguous.
On 2015–07–25, at 11:18 PM, Ryan Johnson <sco...@gmail.com> wrote:The std::indexed_type<Ts,Xs> flavor of magic would be a non-starter, though, because there is no machinery in place to detect a "special" template class name (which could be obfuscated arbitrarily by use of typedefs and template aliases).
Again, indexed_type is an alias template, so it vanishes before deduction begins. indexed_type and [[pack_index(Xs)]] are one and the same solution. There’s no class template involved.
Using std::make_index_sequence just moves the recursion from the call site into library code, which changes nothing from the compiler's perspective (see below). That's what I meant when I described it as "hiding some kinds of recursion in a library class."This proposal is just trying to eliminate a source of recursion and/or helper functions that these previous advances don't seem to have covered. The main messiness that would be left is std::tuple and std::get, both of which seem pretty difficult to achieve non-recursively.
The proposal doesn’t eliminate any template recursion, it eliminates [make_]index_sequence uses and attendant helper template declarations.
It’s not really the same, since the library-internal helper gets memoized. All the optimizations you found in libc++, for instance, only matter when first encountering a sequence that’s longer than anything yet seen in the TU.
By "hoisting" I meant the use of an unexpanded parameter pack in a context where one is not otherwise expected (thank you for helping me nail that down with your concise statement). IMO that capability is the main feature of N3728. Without the first-class parameter packs that N3728 proposes, the notion of an unexpanded parameter pack outside any variadic template context---or inside a variadic template context, but in an expression that does not mention any of the formal parameter packs---is completely foreign.
Disembodied template argument lists, and members that evaluate to pack expansions, are nothing to sneeze at, either.
BTW, even if something like N3728 were accepted, it does not provide a clean mechanism for converting a parameter pack into a <size_t...> pack, so this proposal would still be useful. It does close an important gap, though, by providing a way to define std::tuple non-recursively.
N3728 would allow something like
template< std::size_t len >std::size_t ... unpack_sequence = std::make_index_sequence< len >::value ...;
An operator to do the same would be redundant.
I'm not seeing how you could use indexed_type with non-type or template template parameter packs. If you still want to pursue it in spite of the flaws discussed earlier, could you provide an example?
I don’t plan to pursue it, but the non-type pack would need to appear inside a template-id naming a type. For example:
template< int ... i, template< int > class ... tt, std::size_t ... x >void f( std::indexed_type< tt< i >, x > ... );
Eh? It does require a new entity: an unexpanded parameter pack that is not part of any enclosing variadic template definition.If a new keyword is allowed, the best solution is unpack_sequence because it allows you to locally unpack things (even numeric sequences with other pack basis), without introducing any new kind of entity to the language.
Nothing in the language specification requires that unexpanded pack names must occur inside variadic template declarations. That just happens to be the only way to get one.
It’s perhaps a new sort of entity to the implementation, but not to the language.
While it might be tempting to hack it in quietly for this one (very limited) use case, I suspect the Committee would much rather that any new entity be fleshed out fully to avoid surprises and ugliness later on, when people inevitably try to apply the concept in new and unanticipated ways.
What potential complication? The grammar outside templates is the same as the grammar inside. Non-dependent contexts are usually easier to process than dependent ones.
That said, the unpack_sequence idea *is* tempting because it lets you work with subsequences, an ability that __indexof() operator would not provide. But I also feel like ability to slice and dice parameter packs would be better integrated with N3728 or proposed as a separate feature (which would fill the one remaining gap when working with tuples, namely how to implement things like std::get and std::tuple_element non-recursively).
It seems like the proposals in this thread fall into a spectrum of degrees of decoupling between the numbers and the pack. indexof is most tightly coupled because it goes into the pack expansion, indexed_type is less coupled because it goes into the pack declaration, and unpack_sequence is least coupled because it goes anywhere and it only knows about an integral constant expression.
On 2015–07–27, at 9:11 PM, David Krauss <pot...@gmail.com> wrote:Perhaps there should be a DR; since C++11 it makes more sense for the template-like behavior of injected-class-names to be modeled after alias templates.
On 2015–07–27, at 8:40 PM, Ryan Johnson <sco...@gmail.com> wrote:
That's very clever. The recursion is still there---hiding in amalgam_ftor's abuse of std::common_type ---but at least it only has to happen once when a instantiating each kind of tuple that way.
Ah, I forgot that part was recursive. It really shouldn’t be; using declarations should support pack expansion. Anyway, still, it reduces the O(N^2) instantiations that result from applying tuple_element<T,i> over i : [0, N) to just O(N) instantiations.
It also shouldn’t be specializing std::common_type, but it works :P . The year was 2011, I was naive and carefree…
On 2015–07–27, at 9:12 PM, Ryan Johnson <sco...@gmail.com> wrote:The point was that compilers don't have any machinery for detecting "special" template names in that way---whether aliases or classes.
True. N3728 go well beyond what your unpack_sequence would demand. But unpack_sequence opens the genie's bottle so to speak, by creating a disembodied parameter pack.Disembodied template argument lists, and members that evaluate to pack expansions, are nothing to sneeze at, either.
Once people see that disembodied parameter packs are allowed, they would want to use them in other ways other than this one extremely limited use case, and be confused/annoyed to learn that they can't. Then comes another proposal to fill the gaps, but it would be constrained by whatever semantics the existing entity already defined.
Constexpr and template variables would be a very good example of this: all sorts of utility and type traits classes (esp. those descending from std::integral_constant) would be cleaned up significantly if changed to constexpr template variables. But that would break backwards compatibility so we'll be stuck with unnecessarily complex code.
The indexof operator is kind of nice because it's extremely clear about where those numbers come from, and there's no way to accidentally come up with mismatched parameter packs when trying to use it. That means less cognitive overhead for the user and less complexity for the compiler. At the cost of being very direct and difficult to hide behind a template type.
The unpack_sequence is kind of nice because you don't need an "external" parameter pack at all, which widens the number of use cases it can help with. At the cost of introducing a new concept to the language in an incomplete way.
True. N3728 go well beyond what your unpack_sequence would demand. But unpack_sequence opens the genie's bottle so to speak, by creating a disembodied parameter pack.Disembodied template argument lists, and members that evaluate to pack expansions, are nothing to sneeze at, either.
N3728 proposes disembodied argument list syntax like typedef <int, short, void> ts. That’s different from unpack_sequence(N) which is an ordinary pack, but potentially in a non-template context.
Constexpr and template variables would be a very good example of this: all sorts of utility and type traits classes (esp. those descending from std::integral_constant) would be cleaned up significantly if changed to constexpr template variables. But that would break backwards compatibility so we'll be stuck with unnecessarily complex code.
C++14 does have the _v suffix.
The indexof operator is kind of nice because it's extremely clear about where those numbers come from, and there's no way to accidentally come up with mismatched parameter packs when trying to use it. That means less cognitive overhead for the user and less complexity for the compiler. At the cost of being very direct and difficult to hide behind a template type.
The unpack_sequence is kind of nice because you don't need an "external" parameter pack at all, which widens the number of use cases it can help with. At the cost of introducing a new concept to the language in an incomplete way.
Fair assessment.
unpack_sequence is a bit verbose in its own way, but it potentially saves the effort of making any template declaration in the first place. It would be nice to see an apples to apples comparison between indexof and unpack_sequence.
My guess is that the committee would prefer to see all these proposals presented side-by-side and working through identical example problems.
On 2015–07–27, at 4:23 PM, Edward Catmur <e...@catmur.co.uk> wrote:template<std::size_t I, class T> T& tuple_get(tuple_base<I, T>& obj) { return obj.value; }
We’re getting a bit off topic here, but you can’t get class T without using recursion in the form of tuple_element.
template<int I, class T> struct B { T value; };
template<int I, class T> T& get(B<I, T>& b) { return b.value; }
struct X : B<0, int>, B<1, float> {} x;
float& f = get<1>(x); // seems to work
You can implement get without changing __tuple_base, by using overload resolution over indexed dispatch tags, but that adds inefficiency.Naming the derived type tuple<T...> as a template argument merely mentions a preexisting specialization, which doesn’t require any extra compiler resources. Anyway, the same effect could be achieved by nesting tuple_base inside a class parameterized on <T...>.
Here’s a working proof of concept. amalgam_ftor can be found here.
[...]
The complexity here is in forming and using the overload set at the time tuple is instantiated. Hopefully all the bootstrap stuff (overload set, etc) gets garbage collected, and what’s left is an ideal map from numbers, through base classes, to types.
That particular form of the trick requires N instantiations, so it looks less efficient than recursion. Unless you have a parallelized template engine, recursion will win by early exit.
I didn’t say it was surprising. In my experience, when a metaprogram needs random access, an overload set is a better tool. Instantiating very large std::tuple objects is a red flag. std::tuple_element really shouldn’t be a bottleneck. Implementation effort is better spent optimizing the real fundamentals: name lookup, class template instantiation, overload resolution constexpr evaluation, etc. The true performance culprits can be found by running real workloads in profiler, and there’s no sense in second guessing the work of guys who do that.
You actually make a very good point that N3728 only addresses types, not integer sequences, and so is orthogonal in that respect.
I also suspect strongly that N3728 will not mature in time for C++17, so let's drop it from the discussion and focus on stand-alone solutions to the problem at hand.
I'm torn: namespace std is much less crowded than the global namespace, so std::indexof is more likely to be available than a new indexof keyword. But the __indexof() operator is much safer because improper uses fail to compile.
On Monday, 27 July 2015 12:13:29 UTC+1, David Krauss wrote:On 2015–07–27, at 4:23 PM, Edward Catmur <e...@catmur.co.uk> wrote:template<std::size_t I, class T> T& tuple_get(tuple_base<I, T>& obj) { return obj.value; }
We’re getting a bit off topic here, but you can’t get class T without using recursion in the form of tuple_element.
Sorry for the continued derailing; I'd like to know if I'm doing anything illegal. Can't the compiler deduce T from the tuple_base argument?
You can implement get without changing __tuple_base, by using overload resolution over indexed dispatch tags, but that adds inefficiency.
O(N/2) beats O(N) only when the constants are the same, and nonrecursive code can be more amenable to optimization - vectorization as well as parallelization. I guess the only way to tell is profiling.
I didn’t say it was surprising. In my experience, when a metaprogram needs random access, an overload set is a better tool. Instantiating very large std::tuple objects is a red flag. std::tuple_element really shouldn’t be a bottleneck. Implementation effort is better spent optimizing the real fundamentals: name lookup, class template instantiation, overload resolution constexpr evaluation, etc. The true performance culprits can be found by running real workloads in profiler, and there’s no sense in second guessing the work of guys who do that.It's not just about the compiler, though; recursive implementations spill details into error messages and into linker and debug symbols. Stepping through a deep recursive call to std::get is annoying when it could be done in a single frame (or 2 at most).