C++ Logo

std-proposals

Advanced search

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

From: organicoman <organicoman_at_[hidden]>
Date: Tue, 30 Jul 2024 14:08:20 +0400
This technique of apparent and effective type, could be generalized to C style arrays and variable templates.For example:size_t foo(const char* arr){ //inside the function arr type is just a pointer // but it's real type has an array extent N. using eff_type = effective_decltype(arr); return extract_array_size<eff_type>::value;How would this be possible if 'foo' is separately compiled? How can the value returned by 'foo' depend on information only available at the call site, not in the function body? How would you avoid ODR violations, and how would you even implement this?The same way you implement void foo(auto arg); // c++20}template<typename T, typename V >int gInteger = 1234;int main(){ char arr[]="cool_idea"; foo(arr); // prints 9 auto i = gInteger<double,char>; // type of i is integer std::is_same_v< effective_decltype(i), (int)<double, char>)>; //<- this is a new notation to express a variable template }The whole idea is revolving around keeping the real type of each object, even if it decays to pointer ( ex. Functions and Arrays), or it loses the full type signature ( like variable templates, and function templates).This sounds like magic.No, When you are writing a program you are explicitly passing variable names of fixed types between functions and objects, but as soon as you read data from IO, you will rely on a chosen type(picked by yourself) to interpret that data.So, when the compiler is crunching the source code, it keeps record of all declared types,Just the current standard chose to omit and drop the effective type of arrays and functions to be of type pointer (decaying). I guess its a C legacy!But now we have the tools to rectify this loss of information. After all, a C++ source code, is just a series of inspections and manipulation of types. So the more we keep information about types the more it is useful.What you probably want is reflection, which would not allow you to write your size_t foo(const char*) function, but if you write different functions, reflection would allow you to inspect and manipulate more information about your program.The proposed idea will help in implementing reflection.Take this example:template<typename T>size_t type_hash_value = typeid(T).hash_code();void bar(){ /* we will use the apparent type of a variable to instantiate a vector. And its effect type to record the type*/ /* remember the new notation of a variable template mentioned in example above: (type_id)<Ts...> */ std::vector< (size_t)<T> > vec; vec.push_back(type_hash_value<int>); vec.push_back(type_hash_value<double>); vec.push_back(type_hash_value<someT>); size_t val; std::cin >> val; // IO input vec.push_back(val); /*apparent type of val is the same as its effective type => val is not a variable template yet it is still a valid entry to the vector*/ for(const auto& elem: vec) if ( std::is_same_v<effective_decltype(elem), (size_t)<double>> ) return elem;}The elements of the vector above have two types of information: compile time and runtime.The size and alignment of the elements is the same as std::size_t, that's for runtime.The effective type of the elements is available at compile time only.With this facility you can implement a program wide type_id hash table, and you can go back and forth between type<->value.It is worth mentioning that, if the effective type and the apparent type are the same, this means that the inspected object is not templated, nor decayed.I'm not 100% sure how this would be implemented, i need more research, but i guess compiler wizards out there have the necessary knowledge. For now it is just a floating idea.See "Reflection for C++26" https://wg21.link/p2996 for the active work in this space.Yes, I'm already following its progress closely.  Hello Gents,Let:O(f) be the Set of 'function overloads' of the function named "f".Let:"Ret" be the return type of function "f".The elements of O(f) can have 2 forms:1-> Ret f ( Vargs ) 2-> Ret f <Ts...> ( Fargs ) Where:1-> "Vargs" is a variadic list of arguments types, participating or not in template type arguments deduction if "f" is a template. e.g: template<typename T, typename V>void f(T, V, double, int)Vargs = {T, V, double, int}2-> "Fargs" is a fixed list of arguments types, not related to any template type parameter. e.g: template <typename T, typename V>void f(double, int)Fargs= {double, int} // T,V are not in the list."Ts" is just a variadic list of template parameters types.The type of any function of the 1st form is:decltype (f) = Ret(*)(Vargs), which keeps information about the function's type template parameters participating in the function argument's list.But the type of the 2nd form is:decltype (f) = Ret(*)(Fargs);No mater what the template parameters are, the type of the 2nd form always decays to:Ret(*)(Fargs)And always lose any type information about the function's type template parametersYet, when we want to get the address of such function, we are obligated to use the template types in the function name. e.g:auto select_f = &f<Ts...>;Otherwise we get overload ambiguity, This is a proof that the template arguments participates in the function type.In my opinion, the compiler should keep the template arguments type information.I know that changing the type of "select_f" in the example above will break a lot of code.But i have a suggestion. If the compiler can keep record of :* an apparent function type ; (the usual one) decltype (&f<Ts...>) = Ret(*)(Fargs)* and an effective function type which is: decltype (&f<Ts...>) = Ret(*)<Ts...>(Fargs) This would fix the problem without breaking any neck.Why is this useful?Take this example:struct Erased{std::any (*m_fun) (void);template<auto Func>constexpr Erased() : m_fun(Func){ }auto operator ()(){ using f_type = effective_decltype (m_fun); // imagin we have a type traits that // extracts template types. using T = extract_1st_template_type<f_type>; return std::any_cast<T>( m_fun(void) );}};template <typename Ret>std::any foo(){ return Ret{}; }int main(){ std::vector<Erased> vec; vec.push_back(Erased<&foo<int>>{}); vec.push_back(Erased<&foo<double>>{}); vec.push_back(Erased<&foo<some_type>>{}); for(const auto& elem: vec) DoSomethingBasedOnReturnType(elem());}Using this technique we can store the template type, then recall it back.I guess it will make type erasure more effecient.Any thoughts?RegardsNadirSent from my Galaxy--
Std-Proposals mailing list
Std-Proposals_at_[hidden]
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals

Received on 2024-07-30 10:08:34