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 18:59:50 -0300
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.




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:00:08