#Extracting parameter pack from base class

310 messages · Page 1 of 1 (latest)

twilit jacinth
#

I have this: ```cpp
template <typename... T>
struct system {
using components_t = std::tuple<T...>;
}

template <typename... T>
struct query {}

template <typename S>
system_id create_system() {
return create_system<S>(std::make_index_sequence<std::tuple_size_v<typename S::components_t>> {});
}

template <typename S, size_t... I>
system_id create_system(std::index_sequence<I...>) {
using components_t = typename S::components_t;
static_assert(std::is_base_of_v<system<std::tuple_element_t<I, components_t>...>, S>, "system must derive from system");
auto query_instance = query<std::tuple_element_t<I, components_t>...>();
}```

I used chatGPT to get here, and it works, but it seems awkward, is there a better way of doing this? Do i really need to store the T... in a tuple? And use a helper function with index_sequence to create a new parameter pack from the tuple with I and tuple_element_t? I feel like there should be a more straightforward way...

pastel parcelBOT
#

When your question is answered use !solved to mark the question as resolved.

Remember to ask specific questions, provide necessary details, and reduce your question to its simplest form. For tips on how to ask a good question run !howto ask.

proud dagger
#

what is this actually supposed to achieve?

#

this seems convoluted af ^^

#

is this just about creating a query<T...> with T... that match those of the system<T...>?

twilit jacinth
#

yes, yes it is

proud dagger
#

i would question whether that really is a problem you should be having in the first place.

twilit jacinth
#

without making the caller of create_system pass each component type of the system, i want them to only have to pass the type of the overall system

proud dagger
#

if there's this inherent dependency between those two, why not give the system a member function that creates the query?

twilit jacinth
#

systems look like this: ```cpp
struct movement_system : public system<position, const velocity> {
void run(ctx_t& ctx, query_t& query) override {
for (auto [entity, position, velocity] : query) {
// move
}
}
};


I need to register this system with the world (`create_system`) so it can be called. When registering, i need to create a query with the same `<position, const velocity>`
proud dagger
#

well, simplest would be to just give system a member function that creates the query

#

problem solved

twilit jacinth
#

i could have the system call world.create_query, which would solve both those issues, but would then create a circular dependency

proud dagger
#

you could also do it using a helper with partial specialization

#

but yeah

#

i'm not sure what that would get you over just doing it in the system

twilit jacinth
#

yeah i guess really i should just do it that way for now. if i wanna get crazy with queries it would probably make more sense to just make a query_manager class, and both the world and system could have access to it, solving the circular dependency

#

but im def not doing anything fancy for awhile/ever so i should probably just keep it simple lol

#

thanks for the xy reminder. thats a huge problem when using chatgpt and not people, you go down these rediculous rabbit holes

#

at least i understand weird template stuff slightly more i guess

pastel parcelBOT
#

@twilit jacinth Has your question been resolved? If so, run !solved :)

proud dagger
#

fwiw, here's the simpler way to achieve what you were trying to do:

template <typename... T>
struct system {};

template <typename... T>
struct query {}

template <typename S>
struct query_type;

template <typename... T>
struct query_type<system<T...>> { using type = query<T...>; };

template <typename S>
system_id create_system() {
    auto query_instance = typename::query_type::type<S>();

    …
}
#

@twilit jacinth

twilit jacinth
#

holy shit that makes so much more sense

#

i didnt think about using a specialization like that to grab the types from system. thanks!

proud dagger
#

np ^^

#

it's kinda becoming a lost art these days, it's just constexpr everything, people forget about the power of declarative programming 😛

twilit jacinth
#

when i started cpp i thought it was hard because of having to manually manage memory. nope the hard part of c++ is the crazy new typing features they are adding every update. c++ typing system is so similar to typescripts at this point

final robin
#

the type system is the coolest part

#

and this stuff isnt really new

#

its the classic way to do things

#

but yea they do add a lot of complicated features

#

i like it that way tho

chrome geyser
#

on another note, if you're doing this kinda stuff then I recommend Boost.Mp11 (or one of the many other metaprogramming libraries that exist)

twilit jacinth
#

Yeah i didnt mean dot's solution was new or difficult, i just meant the other stuff clouding it like all the sfinae/concept stuff

#

i should really take some time to properly learn them instead of trying to cobble knowledge together from a million different stackoverflow answers

final robin
#

sfinae also isnt really new

chrome geyser
#
template <typename... T>
struct system {};

template <typename... T>
struct query {};

template <typename S>
system_id create_system() {
  using namespace boost::mp11;
  auto query_instance = mp_rename<S, query>{};
  // ...
}
final robin
#

i got it really fast that way

#

the same goes for any concept tbf

twilit jacinth
#

should i learn sfinae? or concepts? or do i need to learn both? are they even mutually exclusive or do you need to use both?

chrome geyser
#

if you're using C++20, then 99% of the time you'll be wanting concepts instead of SFINAE stuff

twilit jacinth
#

yeah i use cpp20 99% of the time

chrome geyser
#

they can do pretty much everything SFINAE can but better

final robin
#

i rarely use sfinae

proud dagger
final robin
#

you can avoid it with other tricks internally

#

ofc when its exposed to the user directly you may have to use it

chrome geyser
#

SFINAE can have better compile times currently

proud dagger
chrome geyser
#

otherwise I don't really know of anything where SFINAE (as the technique itself) can be used as an improvement

twilit jacinth
final robin
twilit jacinth
#

if its objectively worse?

final robin
#

no?

#

how would you even go about that

proud dagger
twilit jacinth
#

ah

final robin
#

yea

chrome geyser
# proud dagger huh?

it depends on the particular context, but I have measured cases where using expression SFINAE in a return type had better throughput than expressing the same thing using a concept or requires-expression

proud dagger
#

so no, it's not gonna be deprecated

proud dagger
chrome geyser
#

depends what you mean by SFINAE too

#

enable_if I don't see being useful anywhere anymore ever

#

but SFINAE is more than just enable_if

final robin
#

without just saying "dont do this"

#

which would also be valid

chrome geyser
#

(arguably, requires-clauses are a particular form of SFINAE too 😛 )

proud dagger
#

SFINAE kinda exists for good reasons, it wouldn't make sense to make that invalid

final robin
#

ye

proud dagger
final robin
chrome geyser
#

yeah, I'm not saying they're equivalent

final robin
#

ik

chrome geyser
#

just that when substituting parameters in, failed requires-clauses also cause a substitution failure but no error

#

so in the most literal sense of the word...

final robin
#

true true

proud dagger
#

but yeah, raw sfinae without relying on the classic machinery, i can see that that might sometimes be faster than going into the constraints checking path

final robin
#

but its become attached to something specific

#

so it might be confusing for new metaprogrammers 😛

chrome geyser
#

yeah, which I guess is why I was thinking about what "SFINAE" meant in this context

#

a lot of people by SFINAE just mean enable_if (or related tricks), which is definitely no longer a thing IMO

#

but there are uses for it outside of that too

proud dagger
#

yeah, that's what i was mainly thinking of because that's what most people usually mean ^^

final robin
chrome geyser
#

unless they're supporting pre-C++20, they deserve all the pain they inflict upon themselves

proud dagger
#

i am kinda surprised still that constraints would be slower than doing the same thing with sfinae. might just be an artifact of how current implementations work?

final robin
#

i assume it is

#

will probably be ironed out later

chrome geyser
#

possibly yeah; I imagine that perf will improve over time

proud dagger
#

at least i don't really see any inherent reason why constraints would have to be slower

chrome geyser
#

Using concepts will always be slower because that does more work though

proud dagger
#

sure

chrome geyser
#

but normal constraints are different

final robin
#

i think its like why gcc is slower than clang
its just how they do X, its not intrinsically linked to it

proud dagger
#

well, i mean, i would hope that concepts would be faster than doing the equivalent thing via traits and stuff

chrome geyser
#

Depends on the trait 😛 A lot of concepts are implemented in terms of traits, after all..

proud dagger
#

yes, i'm thinking constraints that don't rely on traits

chrome geyser
#

and they have additional partial ordering/normalisation to do

proud dagger
#

yes but they should cut down a lot on template instantiations

chrome geyser
#

Compilers these days are pretty good at traits being implemented as compiler builtins too

proud dagger
#

yeah

final robin
#

yep

#

no more manual integer_sequence ing

#

probably my favorite since i use it so much

proud dagger
#

just saying, if you take a random concept and compare that to implementing the same thing via a traits class and sfinae, i would hope that the concept wins or at least that there be no reason why it would inherently have to be slower 🤔

#

i can very much see why it currently would be

#

we've been doing traits for the last 30 years after all

chrome geyser
#

GCC13 even will have a builtin for tuple_element too (well, probably), which will be handy given how often tuples end up getting used in metaprogramming

proud dagger
#

built-in tuple when 😛

final robin
#

i ended up writing my own tuple to avoid the problems with the standards implementation

chrome geyser
final robin
#

there are even faster ones but its my baby

#

i have to use it

proud dagger
chrome geyser
#

maybe in C++26

proud dagger
#

class member packs when

chrome geyser
#

it is proposed

proud dagger
#

oh

proud dagger
#

nice

#

didn't know

final robin
#

theres a massive proposal with a bunch of metaprogramming features

chrome geyser
#

There's also a clang fork with an impl already

proud dagger
#

sweet

final robin
#

i think pack indexing will also be good

#

its a pain in the ass to deal with rn

proud dagger
#

then yeah, maybe tuple can actually be implemented as a library feature in a sane way eventually

chrome geyser
#

pack indexing? You mean, forward_as_tuple and then std::get

#

(/s)

final robin
#

thank god i didnt have to write it out lmao

#

pack syntax isnt exactly terse

#

also speaking of speed i had a theory about clangs speed and how it affects template behaviour
explained here https://twitter.com/888IGHTFOLD/status/1613324366141394944
do you know anything about this @chrome geyser

Interesting bug I found:
this compiles,
https://t.co/fD5L4581ld
this does not.
https://t.co/Lii4bU9nYZ
[https://t.co/uA15R3eEMS] implies this is in fact ifndr, and msvc and gcc both get this wrong. This is interesting because it shows how different compilers do instantiation.

chrome geyser
#

(I'll ignore that it's impossible for any compiler to ever get something being IFNDR "wrong", given an IFNDR literally means that the compiler can do whatever the hell it likes with it)

#

But yeah, it's well known that different compilers perform different amounts of speculative template instantiation in places; there are some rules around what is allowed to produce errors though

final robin
#

well i meant more how it instantiated it instead of giving an error

#

which is ifndr, not instantiating it and giving an error would be correct

proud dagger
#

wdym

final robin
#

(my understanding could be wrong ofc)

proud dagger
#

if this is ifndr then it's ifndr

#

there's no correct behavior then

final robin
#

it shouldnt pick a specialization

#

theres no way to deduce it

#

it shouldnt even compile

final robin
#

im not even sure what the correct behaviour is when dealing with that
clang only does it the expected way in main

proud dagger
#

ok it's quite late, i'm not exactly sure i understand what the issue is supposed to be with that example

final robin
#

in c++20 it should generate 2 functions when you use the comparison operator overloads

#

and when you have multiple derived classes that means there are duplicate overloads

#

it shouldnt pick an overload because there is more than one "best fit"

proud dagger
#

ok so it is just that. i was thinking there's some supposed issue with instantiation order or smth there. and i kinda fail to see one.

final robin
#

yea but i was theorizing about why gcc and msvc both find a match

proud dagger
#

because of a bug in their implementation of rewritten operator candidates i guess 😛

final robin
#

in relation to other things
rather than read the source i was trying to link other quirks of the compilers to get an idea of whats actually going wrong

#

but its just a theory

#

a compiler theory

proud dagger
#

i mean, you could just debug gcc to see what exactly is going wrong 😛

#

it's kinda funny in msvc, if you change the dur<Foo<T, Other>> to dur<Foo>, it fails

#

so the injected class name is somehow special

#

i don't even want to know how that makes a difference

final robin
#

yea that was what got me interested

#

my idea was when you do that it makes it regenerate them (or something along those lines)
so it then picks the best match for that class because it only sees those at the time

#

but honestly idk

#

its funny

proud dagger
#

yeah this seems like a really weird bug

final robin
#

i dont feel like submitting a bug report to gcc

#

im sure someone else has already found it

proud dagger
final robin
#

this leaves out the bug tho

proud dagger
#

the bug is that this compiles no?

final robin
#

yea

#

hmm

final robin
#

its not entirely relevant to this issue but it could help?

#

idk

proud dagger
#

no idea

twilit jacinth
#

im not sure if you need to do something extra for the template specialization of query_type to also match derived types?

proud dagger
#

yeah this doesn't handle subclasses

twilit jacinth
#

oh, got it. is it possible for a specialization to handle this? i tried playing around with using concepts for it but i dont understand them enough to apply them in this situation

chrome geyser
#

You could use concepts to write a specialisation for any derived type

final robin
#

or that

chrome geyser
#

However, you could also do mp_rename style for any type of any kind at all, as long as the parameter packs match

#

which it seems is what you want

#
template <typename L, template <typename...> typename C>
struct rename;

template <typename L, typename... Ts, template <typename...> typename C>
struct rename<L<Ts...>, C> { using type = C<Ts...>; };

template <typename L, template <typename...> typename C>
using rename_t = rename<L, C>::type;

// ...

template <typename S>
system_id create_system() {
  auto query_instance = rename_t<S, query>{};
  // ...
}
#

(these kinds of nice tricks are why I was recommending Boost.Mp11 earlier)

#

(fixed typo)

proud dagger
#

this doesn't handle the base class case tho right?

chrome geyser
#

Oh wait, I see, I misunderstood

proud dagger
#

i mean, i would generally try to avoid doing things like that where behavior depends on a type deriving from some base class

#

ime that's usually a bad idea

chrome geyser
#

You could do this by using an entirely different trick which might be neater

proud dagger
#

really the simplest solution here is to just put the method to create the query into the system class, then any derived class will automatically have it

chrome geyser
#
template <typename... T>
struct system {
  // define the query type here!
  using query_type = query<T...>;

  // ...
};
#

yeah

#

and then to get that you can just typename S::query_type and that'll always work

proud dagger
#

this just makes a lot more sense than having external code try to inspect types in order to figure out whether to apply magic or not

twilit jacinth
#

yeah that makes a lot more sense

proud dagger
#

the other way round is just super brittle

#

like, what if the system is a private or protected base

#

should that work or not?

#

it's just… not great

chrome geyser
#

without reflection there's no good way of getting the precise template instantiation of the base class either, I don't think

proud dagger
#

yeah, i also don't think you can really deduce it in that case

twilit jacinth
#

keeping the query_type inside the system makes a lot of sense, i like that

chrome geyser
#

yeah, either that or a (static?) function to create the correct object of that type as dot suggested

proud dagger
#

this is actually one reason i'm already dreading the moment when we have reflection: because people will just start to do stuff like that, and be able to do so, even when it's a really bad idea.

final robin
#

im excited for reflection
the good stuff will be cool and the bad stuff will be fun to read

chrome geyser
#

I mean, I'm less concerned about that because with reflection the whole "trace the base classes and do magic" becomes a whole lot less magic and IMO less of a bad idea

#

in this case when you can invert the responsibilities still a good thing to do so, though

final robin
#

im more worried about the current syntax they chose

chrome geyser
#

that is, reflection will allow you to do it the other way around correctly without awkward compromises

final robin
#

and the speed of it

chrome geyser
#

eh, I think the syntax is fine

final robin
#

have you really messed around with it?

#

it gets very painful with any amount of complexity

#

the revised syntax does look more promising but idk

#

its not there yet

twilit jacinth
#

maybe a stupid idea, but just trying to wrap my head around this stuff, would something like this be possible? ```cpp
template <typename F>
using forward_args_t = F<T...>;

i cant seem to get it to work but maybe i have the syntax wrong?
chrome geyser
#

I'm talking about the revised syntax here btw

#

(which is what I thought you meant by "the current syntax they chose", not the syntax from the 2018 TS or whatever)

final robin
#

but yea ive messed around with the new stuff too

#

and its better

final robin
#

but we really need more metaprogramming syntactic sugar to have it be nice to use

twilit jacinth
final robin
#

this is possible

#

only problem is it would be system<...>::template forward_args_t<F>

chrome geyser
#

Yeah, that's fine; doing typename S::template forward_args_t<query> will give you query<stuff>

final robin
#

not the nicest looking but

#

would work

chrome geyser
#

as you can see that's a bit awks though

final robin
#

you would probably write a helper template for it

#

forward_args<F, ...>

proud dagger
#
    template <template <typename...> typename F>
    using forward_args_t = F<T...>;
final robin
#

would work too

chrome geyser
#

ah yea, no that's needed

#

I read what I expected to be there not what actually was 😛

final robin
#

mmm yea i think theres some newer stuff that lets you avoid that

chrome geyser
#

not that I'm aware of

final robin
#

but i may be confusing it with nttp

twilit jacinth
#

ah ha yeah thats what i was missing, never used template templates before

final robin
#
template <typename T>
struct X {};

template <X x>
using ... = something<x>;
chrome geyser
#

yeah, that's just that NTTPs can do CTAD

#

good times

final robin
#

except apple clang which SUX

#

i tried to do the string literal type interning trick on it but it was giving me shit for trying to use it

proud dagger
final robin
#

my jaw just completely fell off

#

i had to tape it back on 🥲

chrome geyser
#

Yeah, I saw this, and my first thought was "I have a dream" and proceeded to write some absolute jank

#

which wasn't helped by the fact that GCC had literally just added support for this and had all sorts of weird bugs and no text editor knew what on earth this was talking about

#

but... it's handy stuff, if you're into completely nonsense TMP

final robin
#

very much am

final robin
#

so you could use a std::array as a parameter for example

chrome geyser
#

std::array is a structural type?

final robin
#

not according to the compilers :P

chrome geyser
final robin
#

same goes for initializer_list

chrome geyser
#

what on earth would make it non-structural?

#

initializer_list, fine, though that's compiler-dependent

final robin
#

honestly... no clue
its probably the fact its a new feature

#

they havent worked out all the kinks

#

string_view was also acting funny

chrome geyser
#

but std::array is specified as just ```cpp
template <typename T, std::size_t N>
struct array {
// either
T __data[N];

// or
T __data_0;
T __data_1;
// ...
T __data_n;
}

#

(and no implementation uses the second version 😛 )

final robin
#

ye

proud dagger
#

you're getting dangerously close here to reminding me that floats can be template parameters now

#

please don't

final robin
#

but it seems like it doesnt consider it structural in template parameters

#

that could be a bug as well

#

i havent looked into this that much

#

just did a bit of testing on different compilers

chrome geyser
#

;compile -std=c++20 ```cpp
#include <array>

template <auto x>
void foo() {}

int main() {
foo<std::array<int, 4>{}>();
}

untold shardBOT
#
Compilation successful

No output.

chrome geyser
#

seems fine ¯_(ツ)_/¯

final robin
#

oh i think the problem was this

#
#include <array>

template <std::array x>
void foo() {}

int main() {
  foo<{ 1, 2, 3, 4 }>();
}
chrome geyser
#

That's just a deduction problem I think

final robin
#

it did figure out the type

#

but it wasnt a fan of it

untold shardBOT
#
Compilation successful

No output.

proud dagger
#

-.-

final robin
#

hold on lemme find the actual code

chrome geyser
#

I win 😛

final robin
#
#include <array>

template <std::array A>
constexpr auto arr_t = A;

auto a = arr_t<{1, 2, 3}>;
#

was based on the idea of str_t<"string literal here">;

untold shardBOT
#
Assembly Output
a:
  .long 1
  .long 2
  .long 3

chrome geyser
#

¯_(ツ)_/¯

#

Looks like GCC10 fails though

#

(GCC 10 had a whole heap of other related bugs too though, all of which have been fixed since AFAIK)

final robin
#

ye

#

also apple clang is basically clang 11.2
they need to get with the times fr

#

(gonna keep malding about how bad it is until they fix it)

#

i might be confusing compilers

#

been a while

twilit jacinth
#

ended up going with a query_t in the system struct. everything works great and now i can have both systems as structs or lambda functions 😄

thanks for all the help guys

#

!solved