C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Function overload set type information loss

From: Breno Guimarães <brenorg_at_[hidden]>
Date: Sat, 3 Aug 2024 15:57:52 -0300
It's just confusing to read because useType returns T but it's actually
xunion.

So, when someone asks what are the limitations it will make it much clearer
if you answer "yes you can only use the return of useType to call
consumeType or some other function that is tagged".

So you can't write:
auto r = useType(elem);
if (r == 1)

Right? Because same way xunion doesn't have the member function "get", it
also doesnt have a operator== right?
So it would be compilation error, correct?

Em sáb., 3 de ago. de 2024 15:48, organicoman <organicoman_at_[hidden]>
escreveu:

> useType returns the unnamed type xunion, that i explained before.
> And that unnamed type has no .get() member function.
> So either you pass it to consumeType or something similarly tagged, or
> you assign it to a variable with a concrete type like int, double, string,
> struct S.... etc
>
>
>
>
>
> Sent from my Galaxy
>
>
> -------- Original message --------
> From: Breno Guimarães <brenorg_at_[hidden]>
> Date: 8/3/24 10:34 PM (GMT+04:00)
> To: organicoman <organicoman_at_[hidden]>
> Cc: Std-Proposals <std-proposals_at_[hidden]>, Tiago Freire <
> tmiguelf_at_[hidden]>
> Subject: Re: [std-proposals] Function overload set type information loss
>
> Or can you do:
>
> for (auto elem : vec)
> {
> auto r = useType(elem); // now r is not void anymore right? It's some
> type that came from the template instantiation
> auto t = r.get(); // can't do this can you?
> doSomething(t);
> }
>
> Em sáb., 3 de ago. de 2024 15:22, Breno Guimarães <brenorg_at_[hidden]>
> escreveu:
>
>> Oh, so the only way to use this function pointer is by doing
>> consumeType(useType(elem)) ?
>>
>>
>> Em sáb., 3 de ago. de 2024 15:16, organicoman <organicoman_at_[hidden]>
>> escreveu:
>>
>>> Like
>>>
>>> template<class T>
>>> void doSomething(T arg) {}
>>>
>>> ...
>>>
>>> for (auto elem : vec)
>>> {
>>> auto r = elem();
>>> auto t = r.get();
>>> doSomething(t);
>>> }
>>>
>>> Since you don't know the type of "r" at compile time, you don't know the
>>> type of "t" at compile time, so you don't know for which types to
>>> instantiate "doSomething".
>>> You see the loop in blue in your example:
>>> It takes a function pointer from the vector then call.
>>> That function pointer is of type void(*)(void)
>>> So that example is a compile error.
>>> And that's what confused me. So i assumed you are talking about
>>> consumeType and gave the previous explanation, that you considered it was a
>>> diversion.
>>>
>>> I'm trying to understand what are the limitations the user will face
>>> when using such feature.
>>>
>>> There are no limitations as far as i know, except that,
>>> If you use a template function name other than consumeType, then you
>>> have to tag it manually so the pre-pass includes it in code generation.
>>> Otherwise consumeType is the default name that the pre-pass is using to
>>> concatenate the switch cases as i showed you before.
>>>
>>> Keep exploring it. Because there is trick that i really want to show
>>> you, but you need to understand deeply the concept or it will not make
>>> sense to you.
>>>
>>> -------- Original message --------
>>> From: Breno Guimarães <brenorg_at_[hidden]>
>>> Date: 8/3/24 9:54 PM (GMT+04:00)
>>> To: organicoman <organicoman_at_[hidden]>
>>> Cc: Std-Proposals <std-proposals_at_[hidden]>, Tiago Freire <
>>> tmiguelf_at_[hidden]>
>>> Subject: Re: [std-proposals] Function overload set type information loss
>>>
>>> Like
>>>
>>> template<class T>
>>> void doSomething(T arg) {}
>>>
>>> ...
>>>
>>> for (auto elem : vec)
>>> {
>>> auto r = elem();
>>> auto t = r.get();
>>> doSomething(t);
>>> }
>>>
>>> Since you don't know the type of "r" at compile time, you don't know the
>>> type of "t" at compile time, so you don't know for which types to
>>> instantiate "doSomething".
>>>
>>> I'm trying to understand what are the limitations the user will face
>>> when using such feature.
>>> ...
>>>
>>> Em sáb., 3 de ago. de 2024 14:49, Breno Guimarães <brenorg_at_[hidden]>
>>> escreveu:
>>>
>>>> And what would we get for a template taking arbitrary type?
>>>>
>>>> Em sáb., 3 de ago. de 2024 14:43, organicoman <organicoman_at_[hidden]>
>>>> escreveu:
>>>>
>>>>> Not clear at all. You changed my question into another question and
>>>>> answered that instead.
>>>>>
>>>>> I asked for which type we should instantiate the template that is
>>>>> being invoked with argument "t" for which you don't know the type.
>>>>>
>>>>> Are you talking about your example? this one:
>>>>>
>>>>> for (auto elem: vec)
>>>>> {
>>>>> auto r = elem();
>>>>> auto t = r.get(); //<-is this the t you are asking?
>>>>> useType(t);
>>>>> }
>>>>>
>>>>> If it is the case, then it means that you misunderstood the signature
>>>>> of useType....please read the post carefully.
>>>>>
>>>>> useType takes a function pointer, not variable of arbitrary type
>>>>>
>>>>> Look what i wrote: (copy/paste)
>>>>> auto useType(effdecltype(foo) f)
>>>>> {
>>>>> using T = __get_ortho_at__<0>(f);
>>>>> f();
>>>>> return T(0);
>>>>>
>>>>> }
>>>>>
>>>>> If I am mistaken then, copy/paste the code from my example and point
>>>>> where you want more explanation.
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> Not clear at all. You changed my question into another question and
>>>>> answered that instead.
>>>>>
>>>>> I asked for which type we should instantiate the template that is
>>>>> being invoked with argument "t" for which you don't know the type.
>>>>>
>>>>> Em sáb., 3 de ago. de 2024 12:47, organicoman <organicoman_at_[hidden]>
>>>>> escreveu:
>>>>>
>>>>>> Oooh i get you now.
>>>>>> Let me simplify that for you by an example.
>>>>>>
>>>>>> *auto* someFunction();
>>>>>>
>>>>>> Can you tell what this function's return type?
>>>>>>
>>>>>> No.
>>>>>>
>>>>>> So let me give you help about the implementation of this function.
>>>>>>
>>>>>> 1- this function returns an unnamed type.
>>>>>> 2- this unnamed type is an aggregate
>>>>>> 3- at offset 8 bytes there is a union of unknown members count and
>>>>>> types.
>>>>>>
>>>>>> Asked:
>>>>>> Read the value of the union member.
>>>>>>
>>>>>> User asks:
>>>>>> But to what type should i cast that union to?
>>>>>>
>>>>>> Answer:
>>>>>> That's your responsibility.....Unless....
>>>>>> You use my tool.
>>>>>>
>>>>>> Users asks:
>>>>>> What is it?
>>>>>>
>>>>>> Answer:
>>>>>> You create a template function, you tune its body to execute whatever
>>>>>> you want based of whatever type you want, and my tool will take in charge
>>>>>> the responsibility of which overload it should call.
>>>>>>
>>>>>> That's in basic language what is happening.
>>>>>>
>>>>>> You don't need to know what type in the union.
>>>>>>
>>>>>> If you want to adventure and cast it to a specific type, you have two
>>>>>> possibility:
>>>>>> * can get what you want,
>>>>>> * you can get type mismatch => garbage.
>>>>>>
>>>>>> All that, is orchestrated by the pre-pass, so it is preferable to let
>>>>>> the tool handle the case.
>>>>>>
>>>>>> What about now? Clear?
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>> Sent from my Galaxy
>>>>>>
>>>>>>
>>>>>> -------- Original message --------
>>>>>> From: Breno Guimarães <brenorg_at_[hidden]>
>>>>>> Date: 8/3/24 6:56 PM (GMT+04:00)
>>>>>> To: organicoman <organicoman_at_[hidden]>
>>>>>> Cc: Std-Proposals <std-proposals_at_[hidden]>, Tiago Freire <
>>>>>> tmiguelf_at_[hidden]>
>>>>>> Subject: Re: [std-proposals] Function overload set type information
>>>>>> loss
>>>>>>
>>>>>> You didn't see how that doesn't work yet?
>>>>>> You don't know the possible types inside xunion because of the
>>>>>> multiple TU thing. So you don't know how to fully implement
>>>>>> operator==(xunion,int). Other translation units can't help you implement
>>>>>> that either because they don't even know an operator== is needed since that
>>>>>> is in a body of another cpp file they never saw.
>>>>>>
>>>>>> But let's see a perhaps more obvious example:
>>>>>>
>>>>>> for (auto elem: vec)
>>>>>> {
>>>>>> auto r = elem();
>>>>>> auto t = r.get();
>>>>>> useType(t);
>>>>>> }
>>>>>>
>>>>>> "r" is the fancy xunion. The you call get on it. So you need to do
>>>>>> the virtual dispatch thing. But you don't know the type of "t", because
>>>>>> different types will return different things when you call "get()" on them.
>>>>>> So how would you know for which types to instantiate useType?
>>>>>>
>>>>>> Em sáb., 3 de ago. de 2024 11:22, organicoman <organicoman_at_[hidden]>
>>>>>> escreveu:
>>>>>>
>>>>>>> I don't think I was clear enough.
>>>>>>>
>>>>>>> What if you have:
>>>>>>> for (auto elem : vec)
>>>>>>> {
>>>>>>> auto r = useType(elem);
>>>>>>> if (r == 1)
>>>>>>> consumeType(r);
>>>>>>> }
>>>>>>>
>>>>>>> The compiler on this translation unit doesn't know all possible
>>>>>>> types (because the vec is global and filled in other TUs like you
>>>>>>> antecipated).
>>>>>>>
>>>>>>> So this translation unit could reason about the types it knows and
>>>>>>> generate the code for "if (r == 1)" but what about the types it doesn't
>>>>>>> know?
>>>>>>> The other translation units don't know the body of the for-loop so
>>>>>>> they wouldn't be able to fill in that gap either.
>>>>>>>
>>>>>>> In this translation unit we could generate the if for "int" but
>>>>>>> another one could require for float. Another translation unit could even
>>>>>>> push a std::string which would make this code a syntax error.
>>>>>>>
>>>>>>> That's why I asked if it was a limitation that you couldn't do
>>>>>>> anything other than directly call consumeType.
>>>>>>>
>>>>>>> The temporary value generated by useType would also have to be
>>>>>>> destroyed and that's a variation of the if problem because it typically is
>>>>>>> destroyed on the caller body. So you wouldn't know which destructor to call.
>>>>>>>
>>>>>>> I didn't understand your question very well, but i will use your
>>>>>>> example and try to detail it.
>>>>>>>
>>>>>>> So let analyze this:
>>>>>>>
>>>>>>> if(r == 1)
>>>>>>>
>>>>>>> For the compiler, it sees it as
>>>>>>>
>>>>>>> if(operator == (r, 1))// bool operator==(xunion,int)
>>>>>>>
>>>>>>> It directly replace it by the cast i described previously.
>>>>>>> And it will become
>>>>>>>
>>>>>>> if(
>>>>>>> *reinterpret_cast<int*>
>>>>>>> ((reinterpret_cast<char*>(&(r))+sizeof(void*))
>>>>>>> == 1)
>>>>>>>
>>>>>>> And it becomes a regular comparison, calling
>>>>>>>
>>>>>>> bool operator(int, int)
>>>>>>>
>>>>>>> Remember what i said in that post:(copy/past)
>>>>>>>
>>>>>>> ------start------
>>>>>>> "...
>>>>>>> In the second case ( casting) which is the easiest, that line will
>>>>>>> be replaced by
>>>>>>>
>>>>>>> double var2 = *reinterpret_cast<double*>
>>>>>>> ((reinterpret_cast<char*>(&(useType()))+sizeof(int)); // int since
>>>>>>> __X_index is int
>>>>>>>
>>>>>>> No mater what is the union's active member.
>>>>>>> The user have to take responsibility of his code.
>>>>>>> And it is safe to still the guts of a temporary.
>>>>>>> ..."
>>>>>>> ------end--------
>>>>>>>
>>>>>>> The user takes responsibility to what he wants to cast to.
>>>>>>>
>>>>>>> You should package all operation inside a template function like
>>>>>>> consumeType.
>>>>>>>
>>>>>>> You can think of consumeType as std::visit
>>>>>>> which was written for you by your servant Mr.compiler.
>>>>>>>
>>>>>>> Did I answer your question?
>>>>>>>
>>>>>>> -------- Original message --------
>>>>>>> From: Breno Guimarães <brenorg_at_[hidden]>
>>>>>>> Date: 8/3/24 5:43 PM (GMT+04:00)
>>>>>>> To: organicoman <organicoman_at_[hidden]>
>>>>>>> Cc: Std-Proposals <std-proposals_at_[hidden]>, Tiago Freire <
>>>>>>> tmiguelf_at_[hidden]>
>>>>>>> Subject: Re: [std-proposals] Function overload set type information
>>>>>>> loss
>>>>>>>
>>>>>>> I don't think I was clear enough.
>>>>>>>
>>>>>>> What if you have:
>>>>>>> for (auto elem : vec)
>>>>>>> {
>>>>>>> auto r = useType(elem);
>>>>>>> if (r == 1)
>>>>>>> consumeType(r);
>>>>>>> }
>>>>>>>
>>>>>>> The compiler on this translation unit doesn't know all possible
>>>>>>> types (because the vec is global and filled in other TUs like you
>>>>>>> antecipated).
>>>>>>>
>>>>>>> So this translation unit could reason about the types it knows and
>>>>>>> generate the code for "if (r == 1)" but what about the types it doesn't
>>>>>>> know?
>>>>>>> The other translation units don't know the body of the for-loop so
>>>>>>> they wouldn't be able to fill in that gap either.
>>>>>>>
>>>>>>> In this translation unit we could generate the if for "int" but
>>>>>>> another one could require for float. Another translation unit could even
>>>>>>> push a std::string which would make this code a syntax error.
>>>>>>>
>>>>>>> That's why I asked if it was a limitation that you couldn't do
>>>>>>> anything other than directly call consumeType.
>>>>>>>
>>>>>>> The temporary value generated by useType would also have to be
>>>>>>> destroyed and that's a variation of the if problem because it typically is
>>>>>>> destroyed on the caller body. So you wouldn't know which destructor to call.
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> Em sáb., 3 de ago. de 2024 10:08, organicoman <organicoman_at_[hidden]>
>>>>>>> escreveu:
>>>>>>>
>>>>>>>> That means you can only use these pointers with consumeType?
>>>>>>>>
>>>>>>>> If you do a for loop on the vec you can only call
>>>>>>>> consumeType(useType(F))?
>>>>>>>> Because if you do other things inside the for loop in one
>>>>>>>> translation unit, the compiler won't have visibility of that in the second
>>>>>>>> translation unit. So it would not know to generate the code for the for
>>>>>>>> loop body in the first translation unit for the types in the second
>>>>>>>> translation unit.
>>>>>>>>
>>>>>>>> Would that be a limitation? Or is there another way around that?
>>>>>>>>
>>>>>>>> As far as the vector is concerned, its elements type is void(*)(),
>>>>>>>> check the algorithm step 10-2
>>>>>>>>
>>>>>>>> And if you recall in the explanation of that algorithm, i said:
>>>>>>>> (copy/paste)
>>>>>>>>
>>>>>>>> -------- start---------
>>>>>>>>
>>>>>>>> "....
>>>>>>>>
>>>>>>>> If useType return value is assigned to a variable we have two cases.
>>>>>>>>
>>>>>>>> auto var1 = useType (F); // perfect capture
>>>>>>>> double var2 = useType(F); // casting
>>>>>>>>
>>>>>>>> The first case, which is the most interesting and powerful, is most
>>>>>>>> useful if you pass that var1 to a function template, consumeType, in our
>>>>>>>> example above
>>>>>>>> ...."
>>>>>>>> -------end-----
>>>>>>>>
>>>>>>>> So, all revolves around useType, and its usage scope.
>>>>>>>>
>>>>>>>> Also, since the vector is a local variable to that translation
>>>>>>>> unit, then the scope of useType is just the instantiated foo<T> in
>>>>>>>> that unit. The rest is irrelevant.
>>>>>>>> You don't pay for what you don't need.
>>>>>>>>
>>>>>>>> Allow me to anticipate one question.
>>>>>>>>
>>>>>>>> What if the vector was a global variable, filled accross
>>>>>>>> translation unit?
>>>>>>>> *It doesn't matter*.
>>>>>>>> The linker is doing the concatenation algorithm that i described to
>>>>>>>> you, so all is fine and dandy.
>>>>>>>>
>>>>>>>> Let's use an example:
>>>>>>>> -----Source1.cpp
>>>>>>>> #include "*temp.h*"
>>>>>>>>
>>>>>>>> extern vector<effdecltype(foo)> globalVector;
>>>>>>>>
>>>>>>>> /// no local insertion into the global vector
>>>>>>>> /// use it directly in the loop
>>>>>>>> for(F : globalVector)
>>>>>>>> consumeType(useType(F));
>>>>>>>>
>>>>>>>> -----Source2.cpp
>>>>>>>> #include "*temp.h*"
>>>>>>>>
>>>>>>>> extern vector<effdecltype(foo)> globalVector;
>>>>>>>>
>>>>>>>> /// just fill the global vector...
>>>>>>>> globalVec.push_back(foo<int>);
>>>>>>>> globalVec.push_back(foo<double>);
>>>>>>>>
>>>>>>>> *Explanation*:
>>>>>>>> In the 1st translation unit, the virtual overload dispatch
>>>>>>>> mechanism (read explanation in the post about the pre-pass
>>>>>>>> algorithm)
>>>>>>>> sees that, in this translation unit, the compiler didn't record any
>>>>>>>> instance of foo<T>, so it emits an error (substitution failure
>>>>>>>> cannot call a template function consumeType), *that if the vector
>>>>>>>> was a local variable*, but because the compiler knows it is a
>>>>>>>> global variable (remember that the vector is the entity which
>>>>>>>> triggered this mechanism), then it replaces the whole overload
>>>>>>>> dispatch switch with just the name of the generated function and let the
>>>>>>>> linker resolve it. Like the following.
>>>>>>>>
>>>>>>>> ----starts like this
>>>>>>>>
>>>>>>>> for(F: globalVector)
>>>>>>>> consumeType(useType(F));
>>>>>>>>
>>>>>>>> ....becomes
>>>>>>>>
>>>>>>>> // generared pre-pass code
>>>>>>>> extern struct __X_foo_eff;
>>>>>>>> __X_foo_eff useType(effdecltype(foo));
>>>>>>>> void __X_consumeType(__X_foo_eff);
>>>>>>>>
>>>>>>>> // down in code
>>>>>>>> for(F: globalVector)
>>>>>>>> __X_consumeType(useType(F));
>>>>>>>>
>>>>>>>> The rest is known to everyone.
>>>>>>>>
>>>>>>>> The overall observation about this mechanism, is that, it doesn't
>>>>>>>> leak type information to the runtime.
>>>>>>>> I don't have a table let say
>>>>>>>> hash(type)<->function_ptr
>>>>>>>> Or
>>>>>>>> textMangle(type)<->function_ptr
>>>>>>>> Usually used by std::any or, some reflection algorithms.
>>>>>>>> Which make this approach safe against reverse engineering the
>>>>>>>> {hash, text}, and doesn't break the ASLR safety mechanism by saving
>>>>>>>> function pointer in permanent global container.
>>>>>>>>
>>>>>>>> I hope it is clear..... at this point, if everyone understood the
>>>>>>>> fundamental of this paper then I guess that you can start contributing.
>>>>>>>>
>>>>>>>> That's all.
>>>>>>>>
>>>>>>>> Em sex., 2 de ago. de 2024 23:25, organicoman <organicoman_at_[hidden]>
>>>>>>>> escreveu:
>>>>>>>>
>>>>>>>>>
>>>>>>>>> So how would you generate the switch for each type given that some
>>>>>>>>> instantiations of the template can happen only in another translation unit?
>>>>>>>>>
>>>>>>>>> Let me illustrate with an example:
>>>>>>>>>
>>>>>>>>> Let's put in header file
>>>>>>>>>
>>>>>>>>> --- *temp.h*
>>>>>>>>>
>>>>>>>>> foo<T>
>>>>>>>>>
>>>>>>>>> consumeType <T>
>>>>>>>>>
>>>>>>>>> useType(effdecltype(foo) f)
>>>>>>>>>
>>>>>>>>> --- source1.cpp
>>>>>>>>>
>>>>>>>>> #include "*temp.h*"
>>>>>>>>>
>>>>>>>>> ///// adding instances
>>>>>>>>>
>>>>>>>>> vec.push_back(foo<int>);
>>>>>>>>>
>>>>>>>>> vec.push_back (foo<double>);
>>>>>>>>>
>>>>>>>>> /// calling function inside loop
>>>>>>>>>
>>>>>>>>> consumeType(useType(F)); //<- to be replaced
>>>>>>>>>
>>>>>>>>> --- source2.cpp
>>>>>>>>>
>>>>>>>>> #include "*temp.h*"
>>>>>>>>>
>>>>>>>>> ///// adding instances
>>>>>>>>>
>>>>>>>>> vec.push_back(foo<float>);
>>>>>>>>>
>>>>>>>>> vec.push_back (foo<char>);
>>>>>>>>>
>>>>>>>>> /// calling function inside loop
>>>>>>>>>
>>>>>>>>> consumeType(useType(F)); //<- to be replaced
>>>>>>>>> *Compilation pre-pass:*
>>>>>>>>> Source1: pseudo code only (don't hate me)
>>>>>>>>> useType:
>>>>>>>>> case foo<int>:
>>>>>>>>> return xunion{int}
>>>>>>>>> case foo<double>:
>>>>>>>>> return xunion{double};
>>>>>>>>> Source2:
>>>>>>>>> useType:
>>>>>>>>> case foo<float>:
>>>>>>>>> return xunion{float}
>>>>>>>>> case foo<char>:
>>>>>>>>> return xunion{char}
>>>>>>>>>
>>>>>>>>> *Compilation pass:*
>>>>>>>>> Since useType is tagged then add a bogus jump instruction that
>>>>>>>>> will never be taken in that translation unit.
>>>>>>>>> Source1.o: pseudo assembly
>>>>>>>>> cmp esi, foo<int>;
>>>>>>>>> je LBL_xunion_int ;
>>>>>>>>> cmp esi,foo<double>
>>>>>>>>> je LBL_xunion_double
>>>>>>>>> jmp 0xfffffff; // hook jump
>>>>>>>>> source2.o:
>>>>>>>>> cmp esi, foo<float>
>>>>>>>>> je LBL_xunion_float
>>>>>>>>> cmp esi, foo<char>
>>>>>>>>> je LBL_xunion<char>
>>>>>>>>> jmp 0xfffffff // hook jump
>>>>>>>>> *Linker: source1.o source2.o main.o*
>>>>>>>>> 1- read mangled name of function
>>>>>>>>> 2- is it tagged as effdecltype
>>>>>>>>> no: do your usual job, then goto 6
>>>>>>>>> 3- is there any previous cmp address saved?
>>>>>>>>> yes: replace the 0xfffff of the hook jmp with the address of
>>>>>>>>> the saved cmp.
>>>>>>>>> 4- save the current first cmp address.
>>>>>>>>> 5- resolve mangled name to be the current function address.
>>>>>>>>> 6- move to next
>>>>>>>>> 7- repeat if not finished.
>>>>>>>>>
>>>>>>>>> *Explanation:*
>>>>>>>>> Let's assume the worst case scenario, where the two translation
>>>>>>>>> unit are compiled separately.
>>>>>>>>> Because if they were together, the compiler detects and fuse the
>>>>>>>>> two generated function assembly into one, by concatenating the switch cases.
>>>>>>>>>
>>>>>>>>> Ok, I guess now, everyone knows what happens in the compiler
>>>>>>>>> pre-pass.
>>>>>>>>> So at the end of it, we will have, for each translation unit an
>>>>>>>>> object file with different switch for the same function name.
>>>>>>>>> Let's tackle the assembly now.
>>>>>>>>> Because the compiler already knows, that useType is a tagged
>>>>>>>>> function, it will add to its assembly a bogus jump instruction, that is
>>>>>>>>> proven it will never be taken for that translation unit only.
>>>>>>>>> jmp 0xFFFFFFFF;
>>>>>>>>> Then if we want to use both *.o object files. Things are taken
>>>>>>>>> differently.
>>>>>>>>> Here comes the linker job.
>>>>>>>>> On the command line, the linker parses the *.o from left to right,
>>>>>>>>> like every one knows.
>>>>>>>>> As soon as it detects the mangled name of useType, it knows that
>>>>>>>>> it is tagged, it saves the address of the first compare instruction cmp
>>>>>>>>> esi, foo<T> then carry on its job, when it enters the second
>>>>>>>>> object file, it finds the same useType mangled name, thus it
>>>>>>>>> knows it is tagged, it checks if it has any compare instruction saved from
>>>>>>>>> previous *.o object file then stamps its address into the bogus jump
>>>>>>>>> instruction of the current object file then saves the first cmp
>>>>>>>>> of the current object file instruction, then marks the mangled name as
>>>>>>>>> resolved with address of useType in this object file.... and so
>>>>>>>>> on....
>>>>>>>>>
>>>>>>>>> current *.o
>>>>>>>>> jmp 0xfffffff <-> becomes <-> jmp lea prev[cmp esi, foo<int>] //
>>>>>>>>> illustration only don't hate me
>>>>>>>>>
>>>>>>>>> until the last object file that contains the same mangled name, at
>>>>>>>>> that point, it created a linked list between compare instructions.
>>>>>>>>> The mangled useType will be resolved to the last same mangled
>>>>>>>>> name seen, thus it becomes the entry point of the useType
>>>>>>>>> function.
>>>>>>>>>
>>>>>>>>> It's like you are concatenating switch statement.
>>>>>>>>> One observation worth reiterating,
>>>>>>>>> Like i said in last reply, we will have code bloat.
>>>>>>>>> That's because the body of useType before the switch statement
>>>>>>>>> will be deduplicated as many as it is generated, and all, except one, will
>>>>>>>>> be execute, the rest is dead meat never executed.
>>>>>>>>> From this, i recommand to make the body of functions like useType
>>>>>>>>> very small, let consumeType do the heavy lifting.
>>>>>>>>> Or make the linker smart to fuse the whole thing into one giant
>>>>>>>>> switch.
>>>>>>>>> But the first recommendation is faster to implement.
>>>>>>>>>
>>>>>>>>> That's one simple approach, many other are way efficient. But we
>>>>>>>>> need a compiler planner to detect best decisions. They will work in tendem.
>>>>>>>>>
>>>>>>>>> That's all.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> Em sex., 2 de ago. de 2024 19:48, organicoman <
>>>>>>>>> organicoman_at_[hidden]> escreveu:
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> So it doesn't work for multiple translation units?
>>>>>>>>>>
>>>>>>>>>> It perfectly does.
>>>>>>>>>>
>>>>>>>>>> Because for each translation unit, you tag the functions and
>>>>>>>>>> generate the code, stamp it, then pass it the compilation step...and so on,
>>>>>>>>>>
>>>>>>>>>> There is no deduplication of function names, nor generation of
>>>>>>>>>> extraneous code. You have exactly what you need in that unit.
>>>>>>>>>>
>>>>>>>>>> Just one thing.
>>>>>>>>>>
>>>>>>>>>> Sometimes the generated code could have the same instructions but
>>>>>>>>>> under different function name, that causes bloating, thus a need to a
>>>>>>>>>> compiler planner.
>>>>>>>>>>
>>>>>>>>>> Look the idea is veeeeeery basic.
>>>>>>>>>>
>>>>>>>>>> I consider a C++ source code, after the compiler does template
>>>>>>>>>> instantiation, as a type's database. Unfortunately the current standards
>>>>>>>>>> does not allow us to query anything from it.
>>>>>>>>>>
>>>>>>>>>> Imagine if you have the power to query how many types your
>>>>>>>>>> program uses, what is the type-size distribution chart, sorting by type
>>>>>>>>>> size, by layout size.... etc
>>>>>>>>>>
>>>>>>>>>> This is a data mine to optimize your code, reason about its
>>>>>>>>>> implementation and make it more fast and efficient.
>>>>>>>>>>
>>>>>>>>>> That's what i am trying to start.
>>>>>>>>>>
>>>>>>>>>> I don't see the extent of my proposal but for sure, with some
>>>>>>>>>> help, it will be developed to full potential.
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> Em sex., 2 de ago. de 2024 19:16, organicoman via Std-Proposals <
>>>>>>>>>> std-proposals_at_[hidden]> escreveu:
>>>>>>>>>>
>>>>>>>>>>> Your solution requires at compile time information that is only
>>>>>>>>>>> available at runtime.
>>>>>>>>>>>
>>>>>>>>>>> I don't understand what runtime data you are referring to.
>>>>>>>>>>>
>>>>>>>>>>> In the algorithm explained before. All the data, that the
>>>>>>>>>>> compiler's pre-pass is manipulating, are compile time data.
>>>>>>>>>>> At the level of a translation unit. A function with *orthogonal
>>>>>>>>>>> template parameters* (foo<T>)
>>>>>>>>>>> cannot exist unless you instantiate it.
>>>>>>>>>>> Add to that, the function is tagged as per the algorithm, so it
>>>>>>>>>>> is prone to record the types it was instantiated for.
>>>>>>>>>>> But as an optimization, the compiler will not record the list of *orthogonal
>>>>>>>>>>> **template parameters* unless it sees that some variable is
>>>>>>>>>>> using it, otherwise no need to generate any code.
>>>>>>>>>>> The compiler confirmed that by the usage of the effdecltype
>>>>>>>>>>> operator inside the template argument of the vector in the
>>>>>>>>>>> example.
>>>>>>>>>>> Every time you instantiate foo with a type, you have to write
>>>>>>>>>>> it explicitly in the source code.
>>>>>>>>>>> foo<int>, foo<double>...etc
>>>>>>>>>>> Next to that, the address of a function is a constexpr value,
>>>>>>>>>>> you can use it as a non-type template parameter.
>>>>>>>>>>> So as a summary, the list of *orthogonal template parameters*
>>>>>>>>>>> is fetchable from the source code, and the address of the instances is
>>>>>>>>>>> constexpr value.
>>>>>>>>>>> These are the ingredients to make the algorithm work.
>>>>>>>>>>>
>>>>>>>>>>> Even if the compiler could trace data across any type of data
>>>>>>>>>>> container (which it won't),
>>>>>>>>>>>
>>>>>>>>>>> It doesn't need to, because the information needed is the *orthogonal
>>>>>>>>>>> template parameters* not the number of elements in the
>>>>>>>>>>> container.
>>>>>>>>>>> You can have 100 element of type foo<int>, but the list of *orthogonal
>>>>>>>>>>> template parameters* is only { int }
>>>>>>>>>>> It's like you are counting how many instances of foo<T> you
>>>>>>>>>>> have in that translation unit.
>>>>>>>>>>>
>>>>>>>>>>> datapoints in container could take the form of any possible
>>>>>>>>>>> combination on any position and be influenced to translation units not
>>>>>>>>>>> visible at compile time. But for your solution to work it requires a fix
>>>>>>>>>>> layout (which in practice doesn't happen) and perfect observability.
>>>>>>>>>>> This is not a problem that needs to be solved, it's just how
>>>>>>>>>>> things work.
>>>>>>>>>>>
>>>>>>>>>>> Let me reiterate.
>>>>>>>>>>> Look at this snippet:
>>>>>>>>>>>
>>>>>>>>>>> vec.push_back(&foo<int>);
>>>>>>>>>>> int random;
>>>>>>>>>>> cin >> random;
>>>>>>>>>>> while(random - -) vec.push_back(&foo<char>);
>>>>>>>>>>> cin >> random;
>>>>>>>>>>> random = min(random, vec.size());
>>>>>>>>>>> while(random - -) vec.pop_back();
>>>>>>>>>>>
>>>>>>>>>>> As you can see, i cannot tell nor track the vector size. But it
>>>>>>>>>>> is irrelevant, because i have only two function pointer => { int, char }
>>>>>>>>>>> And by consequence my switch statment will contain only two
>>>>>>>>>>> cases:
>>>>>>>>>>>
>>>>>>>>>>> case &foo<int>:
>>>>>>>>>>> return __Xic_foo_eff_{ &foo<int>, int(0) };
>>>>>>>>>>> case &foo<char>:
>>>>>>>>>>> return __Xic_foo_eff_{ &foo<char>, char(0) };
>>>>>>>>>>>
>>>>>>>>>>> The two case lables are constexpr values known at compile time.
>>>>>>>>>>>
>>>>>>>>>>> You cannot resolve this problem without appending extra data to
>>>>>>>>>>> containers that is used to inform about the nature of the data. And you
>>>>>>>>>>> cannot solve this without a runtime check and control flow to browse for
>>>>>>>>>>> the right handler.
>>>>>>>>>>> This is a problem of distinguishability and computability, it is
>>>>>>>>>>> not something you can solve, it's a consequence of math as described by
>>>>>>>>>>> Shannon.
>>>>>>>>>>>
>>>>>>>>>>> That's why std:any has additional data related to runtime type
>>>>>>>>>>> information, and that's why it always needs some form of selection or hash
>>>>>>>>>>> table or equivalent.
>>>>>>>>>>>
>>>>>>>>>>> Well, i just proved to you that i don't need that data, and that
>>>>>>>>>>> this approach is plausibly more effecient and safe.
>>>>>>>>>>> Let's try to stretch it more, shall we?
>>>>>>>>>>>
>>>>>>>>>>> The way it's already done is already how this problem should be
>>>>>>>>>>> solved, no new feature necessary. What you are proposing requires the
>>>>>>>>>>> suspension of the laws of physics.
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> ------------------------------
>>>>>>>>>>> *From:* organicoman <organicoman_at_[hidden]>
>>>>>>>>>>> *Sent:* Friday, August 2, 2024 9:44:09 PM
>>>>>>>>>>> *To:* Tiago Freire <tmiguelf_at_[hidden]>;
>>>>>>>>>>> std-proposals_at_[hidden] <std-proposals_at_[hidden]>
>>>>>>>>>>> *Subject:* Re: [std-proposals] Function overload set type
>>>>>>>>>>> information loss
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> And also unimplementable...
>>>>>>>>>>> But you don't have to take my word for it, you can try it
>>>>>>>>>>> yourself.
>>>>>>>>>>>
>>>>>>>>>>> But ... that's the whole purpose of this thread.
>>>>>>>>>>> Putting down an idea in form of a paper, or an algorithm and
>>>>>>>>>>> tackle any contradiction it raise untill we hit the wall or we make it
>>>>>>>>>>> through.
>>>>>>>>>>> And we have just started agreeing about the core idea. Which is
>>>>>>>>>>> Adding an operator that does overload dispatch and code
>>>>>>>>>>> generation...etc
>>>>>>>>>>>
>>>>>>>>>>> If it is not implementable, then point where, i will work on
>>>>>>>>>>> it...
>>>>>>>>>>>
>>>>>>>>>>> ------------------------------
>>>>>>>>>>> *From:* Std-Proposals <std-proposals-bounces_at_[hidden]>
>>>>>>>>>>> on behalf of organicoman via Std-Proposals <
>>>>>>>>>>> std-proposals_at_[hidden]>
>>>>>>>>>>> *Sent:* Friday, August 2, 2024 9:15:36 PM
>>>>>>>>>>> *To:* organicoman via Std-Proposals <
>>>>>>>>>>> std-proposals_at_[hidden]>
>>>>>>>>>>> *Cc:* organicoman <organicoman_at_[hidden]>
>>>>>>>>>>> *Subject:* Re: [std-proposals] Function overload set type
>>>>>>>>>>> information loss
>>>>>>>>>>>
>>>>>>>>>>> Hello Gašper,
>>>>>>>>>>> Since participants are starting to get what I am talking about,
>>>>>>>>>>> then allow me to answer your pending question.
>>>>>>>>>>> This is what you've said in previous post.
>>>>>>>>>>>
>>>>>>>>>>> There is another fundamental misunderstanding here.
>>>>>>>>>>>
>>>>>>>>>>> The feature calls to "record all the orthogonal template
>>>>>>>>>>> parameters".
>>>>>>>>>>>
>>>>>>>>>>> While that's not what that word means, I think regardless it's
>>>>>>>>>>> asking for global enumeration. C++ has a separate linking model. All the
>>>>>>>>>>> types/functions are not known within a single unit. The xunion is
>>>>>>>>>>> impossible to compute.
>>>>>>>>>>>
>>>>>>>>>>> This is a sister problem to what Jason is highlighting.
>>>>>>>>>>>
>>>>>>>>>>> When i was describing my algorithm, i left a big X mark, hoping
>>>>>>>>>>> that someone will ask me, if people were attentive.
>>>>>>>>>>> Line:
>>>>>>>>>>> 4-b-
>>>>>>>>>>> switch
>>>>>>>>>>> *.....
>>>>>>>>>>> * inside function body: goto X
>>>>>>>>>>>
>>>>>>>>>>> Then I left a hint at the bottom of the post in a form of code
>>>>>>>>>>> comment
>>>>>>>>>>>
>>>>>>>>>>> double var2 = *reinterpret_cast<double*>
>>>>>>>>>>> ((reinterpret_cast<char*>(&(useType()))+sizeof(int)); // int
>>>>>>>>>>> since __X_index is int.
>>>>>>>>>>> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>>>>>>>>>>> ^^^^^
>>>>>>>>>>>
>>>>>>>>>>> The __X_index is not of type int but of type void*, the unnamed
>>>>>>>>>>> struct that the compiler generate becomes:
>>>>>>>>>>> struct __Xidfc_foo_eff
>>>>>>>>>>> {
>>>>>>>>>>> void* __X_index;
>>>>>>>>>>> union
>>>>>>>>>>> { int __Xi; double __Xd ; float __Xf; char __Xc };
>>>>>>>>>>> };
>>>>>>>>>>>
>>>>>>>>>>> Since the compiler tagged useType function, as described by the
>>>>>>>>>>> algorithm, then after finishing recording the orthogonal template
>>>>>>>>>>> parameters, it replaces the body with the following.
>>>>>>>>>>>
>>>>>>>>>>> auto useType(effdecltype(foo) f)
>>>>>>>>>>> {
>>>>>>>>>>> f();
>>>>>>>>>>> switch(f)
>>>>>>>>>>> {
>>>>>>>>>>> case &f<int>: //<- the pointer value
>>>>>>>>>>> return __Xidfc_foo_eff{(void*) &f<int>, int(0)};
>>>>>>>>>>>
>>>>>>>>>>> case &f<double>:
>>>>>>>>>>> return __Xidfc_foo_eff{(void*) &f<double>, double(0)};
>>>>>>>>>>>
>>>>>>>>>>> case &f<float>:
>>>>>>>>>>> return __Xidfc_foo_eff{(void*) &f<float>, float(0)};
>>>>>>>>>>>
>>>>>>>>>>> case &f<char>:
>>>>>>>>>>> return __Xidfc_foo_eff{(void*) &f<char>, char(0)};
>>>>>>>>>>> }
>>>>>>>>>>> }
>>>>>>>>>>>
>>>>>>>>>>> Pointers are comparable for equality.
>>>>>>>>>>>
>>>>>>>>>>> As you can see, you can capture the xunion.... right? There is
>>>>>>>>>>> no fundamental misunderstanding ...
>>>>>>>>>>> Plus everything is done by the compiler, there is no global
>>>>>>>>>>> map, no find algorithm, no type hashs, no type erasure (heap allocation),
>>>>>>>>>>> no manual intervention. All is automated at compile-time.
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> --
>>>>>>>>>>> Std-Proposals mailing list
>>>>>>>>>>> Std-Proposals_at_[hidden]
>>>>>>>>>>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>>>>>>>>>>
>>>>>>>>>>>

Received on 2024-08-03 18:58:09