C++ Logo

std-proposals

Advanced search

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

From: Breno Guimarães <brenorg_at_[hidden]>
Date: Wed, 31 Jul 2024 19:28:58 -0300
That's just more feedback dismissal

Em qua., 31 de jul. de 2024 19:13, organicoman <organicoman_at_[hidden]>
escreveu:

>
>
>
> Very well put. I agree with Tiago here.
>
> The feedback given here is being waived-off as "that's not how type theory
> works" or "it doesn't work on the current version of the standard but will
> with my proposal".
>
> Try to absorb and really think about the feedback. Understanding of how
> compilers work, what it can do and what it can't is important even if your
> proposal is about theory. It needs to be implementable.
>
> Things like "and oh my the way, the vector is constexpr even though we
> didn't declare it as such, because all elements can be deduced in compile
> time" make it look like you are creating things on the fly.
>
> How much time did you spend thinking, tinkering and exercising this idea
> before posting here?
> You need to try to break your idea first and see if it stands long before
> it becomes a proposal.
>
> Tiago feed back is accepted with joy, there is no doubt, but i would like
> to attrack the attention that, sitting inside a system too long makes you
> institutionalized. (Quoted from the Shawshank redemption).
>
>
> Em qua., 31 de jul. de 2024 18:38, Tiago Freire via Std-Proposals <
> std-proposals_at_[hidden]> escreveu:
>
>> Look I'm not calling out of in order to try and gate keep you. Or to put
>> you down.
>> But I can tell, it's not a lot.
>> There's no shame of being new to the language, we have all been there.
>> I was a noob once to, and let me tell I had some weird misconceptions
>> about the language to.
>>
>> But unfortunately, this is a standards definition, everybody who has ever
>> written C++ as to deal with stuff that's in the standards. And this
>> includes very smart people, with a life time and legacy of experience. It
>> includes many such people.
>> And I have seen many such smart people fail to put a dent into a new
>> proposal.
>>
>> It is building upon the work of many people that came before. If stuff
>> was this easy, odds are there would been many opportunities for people
>> in the past to have picked it up and do it already. The consequence of this
>> is that as time progresses, whatever is left that can be done gets increasingly
>> harder.
>>
>> It's far more likely that you have done a mistake, than you have stumbled
>> into something new.
>>
>> I'm not saying that it is impossible for someone with little experience
>> to be able to put on an effective
>> proposal that changes something in the standard. But do too the great
>> filter, unless you are working at the very top of the edge you are not
>> likely to succeed.
>>
>> Unfortunately, you have to be this tall to take the ride.
>> Technically speaking, you don't have to, but it is highly recommended.
>>
>> Shit is hard! Even for an expert.
>>
>>
>>
>> One thing that I can tell you immediately from experience is that what
>> you are asking for is impossible to implement. It cannot be done with the
>> C++ model, and it should never be.
>> This is immediately apartment by things like, not knowing what the
>> compiler can know, naive expectations of having compile time information
>> being deduced from runtime data, not understanding how code gets compiled,
>> and not understanding that template parameters (even though can be
>> described with types) they are not a description of the type being declared.
>>
>> You cannot see this because you don't have the experience necessary to
>> understand these things and why they are important.
>>
>>
>> And I have a genuine advice.
>>
>> Spend more time learning the language, try to understand why things are
>> the way they are first. Try to understand what the compiler can know and
>> how it does what it does. It isn't a magical application made by the Gods,
>> people like you and me have written them, and they had to figure out a
>> mechanism to get it to work.
>> If people with experience tell you that it doesn't work this way, you
>> should consider their advice.
>> And if you don't know the technical details on how to get this to work in
>> practice, i.e. what is the compiler should actually do to achieve this
>> result. You should not count on "the smart programming people to do it for
>> you" because they are telling you "it can't be done".
>> And unless you have something concrete in terms "how exactly it can be
>> done", this conversation is pointless.
>>
>>
>>
>>
>>
>> ------------------------------
>> *From:* organicoman <organicoman_at_[hidden]>
>> *Sent:* Wednesday, July 31, 2024 10:53:26 PM
>> *To:* Tiago Freire via Std-Proposals <std-proposals_at_[hidden]>
>> *Cc:* Tiago Freire <tmiguelf_at_[hidden]>
>> *Subject:* Re: [std-proposals] Function overload set type information
>> loss
>>
>>
>>
>>
>>
>> Sent from my Galaxy
>>
>>
>> -------- Original message --------
>> From: Tiago Freire via Std-Proposals <std-proposals_at_[hidden]>
>> Date: 8/1/24 12:44 AM (GMT+04:00)
>> To: std-proposals_at_[hidden]
>> Cc: Tiago Freire <tmiguelf_at_[hidden]>
>> Subject: Re: [std-proposals] Function overload set type information loss
>>
>> Quick question, how many years of experience do you have writing code in
>> C++ at a professional level?
>>
>> Enough to know what I am doing.
>>
>> Now to the meat of the discussion.
>> Do you have any ambiguity?
>>
>>
>>
>>
>> ------------------------------
>> *From:* Std-Proposals <std-proposals-bounces_at_[hidden]> on behalf
>> of organicoman via Std-Proposals <std-proposals_at_[hidden]>
>> *Sent:* Wednesday, July 31, 2024 10:23:07 PM
>> *To:* Sebastian Wittmeier via Std-Proposals <
>> std-proposals_at_[hidden]>
>> *Cc:* organicoman <organicoman_at_[hidden]>
>> *Subject:* Re: [std-proposals] Function overload set type information
>> loss
>>
>> *This is my proposal*(C++17 based)* hopefully somebody will rewrite it
>> in a better standard way.*
>> *----------------------------------------------------*
>>
>> * Operator *
>> * effdecltype(*expression)
>>
>> Other plausible name : *typeof (*expr*)*
>>
>> *Intro*
>> C++ Is a type strong programming language, each object used has a
>> well known type.
>> For all object instantiated/defined in a program there is a one to one
>> mapping between the name and the type it belongs to.
>> Except for 3 categories:
>> 1- Arrays
>> 2- Function templates
>> 3- Variable templates
>>
>> Collapsing type signature into one type id, makes tracing back what was
>> passed/called impossible. And it is an aberration to the strong typing
>> adopted by C++ language.
>> Let's illustrate that with some examples:
>> *Category 1:*
>>
>> #include <type_traits>
>> template<typename T>
>> void foo(T t)
>> {
>> static_assert(std::is_same_v<T, char*>);
>> }
>> int main()
>> {
>> char arr[5];
>> foo(arr); // #1 assertion passed
>> static_assert(std::is_same_v<decltype(arr),
>> char[5]>); // #2 assertion passed
>> }
>>
>> In the example above, the same variable arr has two different types
>> #1 char*
>> #2 char[5]
>> That's what i mean by type signature collapsing. The standard calls it *array
>> pointer decay.*
>>
>> *Category 2:*
>>
>> #include<type_traits>
>> template<typename T>
>> void foo() { }
>>
>> int main()
>> {
>> auto foo_1 = &foo<int>;
>> auto foo_2 = &foo<double>;
>> static_assert
>> (
>> std::is_same_v
>> <
>> decltype(foo_1)
>> , decltype(foo_2)
>> >
>> ); // #1 assertion passed
>> }
>>
>> In this example, we have two functions instances with different type
>> signature, yet their type id is the same.
>>
>> *Category 3:*
>>
>> #include<type_traits>
>> template<typename T>
>> int Var = 123;
>>
>> int main()
>> {
>> auto var_1 = Var<double>;
>> auto var_2 = Var<char>;
>> static_assert
>> (
>> std::is_same_v
>> <
>> decltype(var_1)
>> , decltype(var_2)
>> >
>> ); // #1 assertion passed
>> }
>>
>> The same as well for variable templates.
>> Different signature collapsed to same type id.
>>
>> The standard doesn't explain why it adopted this behavior, except for the
>> case of array pointer decay.
>> But from the examples above, we can observe a recurring pattern.
>>
>> There are *two different types*, one written by the user, and one seen
>> by the compiler.
>>
>> The type written by the user is the real effective type, but the type seen
>> by the compiler is the apparent type over the rest of the program.
>>
>> *Motivation*
>> To preserve the one to one mapping between what the user intend and
>> what the program see, and to keep a way to trace back all calls and usage
>> of any type, we need to solve this duality between effective type and apparent
>> type.
>> Some consequences engendered from this solution, is type introspection
>> and easy type erasure.
>>
>> *Discussion*
>> While analyzing the behavior of the examples given (category 2,
>> category 3). We extracted the following observations:
>>
>> 1- if the template parameters don't participate in the apparent type of
>> the object, a dissociation between the apparent type and the effective
>> type is created.
>> Let us call this kind of template parameter an *othogonal template
>> paramter*
>>
>> 2- All *orthogonal template parameters* contribute in resolving the
>> instance's value, but don't participate in the type id.
>>
>> So mathematically speaking:
>>
>> let:
>> template<typename...Ts> U;
>> a templated type where:
>> U ∈ { category2, category3 }
>> let:
>> S(U) = {Ts...} the set of all template arguments of U.
>> If we prove that there is a sub set A(U) of S(U), where:
>> A(U) ⊆ S(U) ^ A(U) is not empty.
>> ∀P ∈ A(U) ^ P is an othogonal template paramter.
>> then:
>> apparent type(U) is Not the same as effective type(U).
>>
>> Some examples for illustration:
>>
>> #include <type_traits>
>> template<typename T, typename V>
>> void foo(V v) { }
>>
>> int main()
>> {
>> auto f_ptr1= &foo<int, double>;
>> auto f_ptr2= &foo<char, double>;
>> static_assert(std::is_same_v<decltype(f_ptr1), decltype(f_ptr2)>); // assertion
>> passed.
>> }
>>
>> In the above example:
>> S(U) = { T, V }
>> A(U) = { T }
>>
>> A(U) is not empty which implys that:
>> effective type (foo) != apparent type (foo);
>>
>>
>> *Introducing operator effdecltype **(or typeof)*
>> The purpose of this operator is to detect the case described above
>> and record it for the concerned variable, and avoid breaking any code
>> relying on apparent type.
>>
>> *Definitions*
>> This operator has the following properties.
>>
>> Let
>> expr, expr1, expr2 ∈ {category1, category2, category3}
>>
>> 1- If:
>> effdecltype(expr1) same as effdecltype(expr2)
>> Then:
>> decltype(expr1) same as decltype(expr2)
>>
>> 2- casting
>> decltype(effdecltype(expr)) same as decltype(expr);
>> Given this property we can write directly
>> decltype(expr) same as effdecltype(expr)
>> for convenience.
>>
>> 3- If:
>> decltype(expr1) same as decltype(expr2)
>> And
>> effdecltype(expr1) not as effdecltype(expr2)
>> Then:
>> effdecltype(expr1) could be written as
>> decltype (expr1) *<A1(U)>*
>> And:
>> effdecltype(expr2) could be written as
>> decltype (expr2) *<A2(U)>*
>> Where:
>> A1(U) and A2(U) are the set of *orthogonal template parameters* for expr1
>> and expr2 respectively.
>>
>> 4- change of behavior for *auto* keyword :
>> *auto* var = effdecltype(expr);
>> *auto* var2 = expr;
>>
>> *auto* here carries the same meta-data as effdecltype(expr), denoting
>> perfect capturing of the effective type.
>>
>> 5- casting:
>> decltype(expr) var = effdecltype(expr){ expr};
>>
>> This is an explicit cast of the effective type to an apparent type, and
>> the var object type has no extra meta-data.
>>
>> 6- value comparison:
>> if:
>> effdecltype(expr1){ args } == effdecltype(expr2){ args }
>> then:
>> decltype(expr1){ args } == decltype(expr2) { args };
>> And the opposite is also true.
>> Where:
>> args are the arguments to the constructor of the expression's type,
>> which are usually the expression itself.
>>
>> 7- fallback to decltype behavior:
>> If exprX is not one of the 3 categories above.
>> Then:
>> effdecltype(exprX) same as decltype(exprX)
>>
>> In funny summary:
>> effdecltype is decltype with X-ray vision
>>
>> Illustration with examples:
>> (If effdecltype operator is implemented)
>>
>> #include <type_traits>
>> template<typename T, typename V>
>> void foo(V v) { }
>>
>> template<typename X, typename Y>
>> auto Var = Y{123};
>>
>> void
>> bar(effdecltype(Var<float, short>) const& a)
>> {
>> static_assert(std::is_same_v<short,
>> decltype(a)>); // assertion passed
>>
>> static_assert
>> (
>> std::is_same_v
>> <
>> effdecltype(a)
>> , effdecltype(Var<double,short>)
>> >
>> );//passed (must read comment *X**)
>> }
>>
>> template<typename X>
>> using meta_double = effdecltype(Var<X,double>);
>>
>> int main()
>> {
>> auto foo_1 = effdecltype(&foo<int, double>){&foo<int, double>}; //#1
>>
>> void(*foo_2)(char) = effdecltype(&foo<double, char>){ &foo<double,
>> char>}; //#2
>>
>> auto foo_3 = &foo<long long, char>; // #3
>>
>> static_assert
>> (
>> std::is_same_v
>> <
>> decltype(foo_3)
>> , effdecltype(foo_3)
>> >
>> ); // assertion passed (read comment X*)
>>
>> auto var_1 = effdecltype(Var<char, int>){ Var<char, int>};
>> int var_2 = Var<double, int>;
>>
>> if(var_1 == var_2)
>> puts("different effective types, same value"); //printed
>>
>> static_assert
>> (
>> std::is_same_v
>> <
>> decltype(var_1)
>> , effdecltype(var_1)
>> >
>> ); // assertion passed (read comment X*)
>>
>> meta_double meta1 = Var<char,double>; // #4
>> meta_double<float> meta2 = Var<float,double>; // #5
>> }
>>
>> In the example above we illustrated some of the properties of operator
>> effdecltype
>>
>> In effdecltype's type notation we can write
>>
>> *Case#1*: foo_1 is void(*)<int>(double), perfect capture with operator
>> *auto*.
>>
>> *Case#2*: foo_2 is void(*)(char), casting from effective to apparent
>> type.
>>
>> *Case#3*: foo_3 is void(*)<long long>(char), perfect capture with
>> operator *auto*.
>>
>> *Case#4*: meta1 is (double)<char>,
>> First effdecltype was applied to the right hand side of the assignement
>> to deduce the missing *orthogonal template parameter* of the alias type
>> meta_double, via template argument deduction mechanism, then the value
>> of Var is assigned to meta1
>>
>> Case#5: meta2 is (double)<float>,
>> Same as #4 but the *othogonal template paramter* was explicitly
>> provided, if types mismatch a compiler warning is raised.
>> The decision to raise a warning instead of an error, is to denote that,
>> what the user writes is what the program should adopt.
>>
>> *Comment X*:*
>> Since the actual type-traits machinery relys on the apparent type to do
>> its job, then when we use effdecltype inside any type-traits meta
>> function the effective type is cast to the apparent type. Remember
>> effdecltype is just decltype with X-ray vision.
>> Later in the this paper we will show how to fix this situation.
>>
>> *Usage*
>> After defining what the operator does, we have the opportunity, in
>> this part, to show some usage and implications of such operator.
>>
>> To use the operator effdecltype effectively, we need to add some tools
>> to our beloved C++ language.
>>
>> builtin functions*:*
>> template<typename U>
>> constexpr size_t __orthogonal_size__(effdecltype(U{}) = {}) noexpect;
>>
>> Returns the count of *orthogonal template parameters* (size of A(U) defined
>> above.)
>>
>> template<typename U, typename R, size_t I>
>> constexpr R
>> __ortho_type_at__<I>(effdecltype(U{}) = {}) noexcept;
>>
>> Extracts the orthogonal template parameter at index I in the set A(U)
>> defined above.
>>
>> More builtin functions can be defined, but this is what i have in mind
>> now.
>>
>> Tips and tricks:
>> The following is not an exhaustive list of all what we can do with this
>> operator, but just a guid to unleash the user's imagination.
>>
>> *1-store and load types:*
>>
>> #include <vector>
>> template<typename T>
>> inline constexpr int Var= 123;
>>
>> struct S{
>> template<typename T>
>> using meta_int = effdecltype(Var<T>);
>> meta_int m_val;
>> template<typename T>
>> constexpr S() noexcept
>> : m_val (Var<T>)
>> { }
>> };
>>
>> template<typename T>
>> void ActBasedOnType(int val)
>> {
>> std::cout << typeid(T).name() << "->" << val << std::endl;
>> }
>> int main()
>> {
>> std::vector<S> vec;
>> vec.push_back(S<int>{} );
>> vec.push_back(S<double>{} );
>> vec.push_back(S<char>{} );
>>
>> for(const auto& elem: vec)
>> {
>> std::cout << S.m_val << std::endl;
>> } // *prints*: 123\n123\n123\n
>>
>> for(const auto& elem: vec)
>> {
>> auto data = S.s; // uses effdecltype
>> ActBasedOnType<__ortho_type_at__<0>(data)>(data);
>> }// *prints*: int->123\ndouble->123\nchar->123\n
>> }
>>
>> So what is all this black magic??
>> *Explanation:*
>> Observation: You must keep in mind that the 3 categories we are dealing
>> with are global constexpr variables. And when we assign their values to
>> other variables we can decay it to a reference (case of function pointer)
>> if that variable doesn't modify its value, or we stamp a copy of their
>> values (for variable templates if copy needed).
>> Our interest is in the orthogonal template parameters not the value.
>> With this paper addition we are going to exploit that fact deeply.
>>
>> The struct S above defines a templated effective type alias, captured
>> from the global variable Var<T>.
>>
>> We define also a function template which prints the type of its template
>> argument.
>>
>> Inside main we creat a vector of S.
>> sizeof(S) == sizeof(int) and alignof(S) == alignof(int)
>> Also, important to notice, is that vector is constexpr vector even we
>> didn't explicity wrote it.
>> Why? Because all elements of vec are references to a global constexpr
>> variable known at compile time and its size is known at compile time.
>>
>> We push values of type S, using the templated constructor.
>> Since we are instantiating values of Var<T> indirectly, the compiler
>> creates an internal table of mapping between orthogonal template parameter
>> T and an index i in vec.
>>
>> In the first "for loop", the regulare compilation process is performed.
>> m_val is of int type no magic there.
>>
>> In the second "for loop", the compiler unrolls the loop and instantiates
>> a function out of ActBasedOnType<T> with T fetched from the internal
>> structure created when pushing values into the vector.
>> So the loop will be replaced by
>>
>> ActBasedOnType<int>(123);
>> ActBasedOnType<double>(123);
>> ActBasedOnType<char>(123);
>>
>> The compiler has the choice to keep that inner mapping structure for
>> latter use, and it can update the mapping if it sees necessary.
>> Otherwise it can destroy it if it can prove that there is no necessity to
>> keep it.
>>
>> All the magic happens in the unrolling of the loop.
>> Because all information are available at compile time. (Remember the
>> green observation above.)
>> Unrolling the loop is one technique , other techniques could be stamping
>> a switch statment, or implementing a visitation method.... compilers
>> implementers are free to amaze us.
>>
>> FIN.
>>
>>
>> Oh man it's tiresome on a cellphone..🥵
>>
>>
>>
>> --
>> Std-Proposals mailing list
>> Std-Proposals_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>
>

Received on 2024-07-31 22:29:15