C++ Logo

std-proposals

Advanced search

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

From: Jonathan Wakely <cxx_at_[hidden]>
Date: Tue, 30 Jul 2024 13:03:18 +0100
On Tue, 30 Jul 2024 at 11:09, organicoman <organicoman_at_[hidden]> wrote:

>
>
> 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
>

OK, that's creates a function template, equivalent to:

template<typename T> void foo(T arg);

So the equivalent for your example would be a function template too, which
you can already write in C++ today (and all the way back to C++98):

template<std::size_t N> size_t foo(const char (&)[N]) { return N; }

And this already exists as std::size.

There's no need for your effective_decltype and extract_array_size magic,
which are not implementable. For a non-template function, what you want is
not possible. For a template function, you can do it already.

The reason you don't generally want to do that for more complicated
functions is that you get a different foo<N> specialization for every
different string length:

foo("1");
foo("12");
foo("123");

Each of these creates a separate specialization, so generates more code and
increases your binary size.



>
>
> }
>>
>> template<typename T, typename V >
>> int gInteger = 1234;
>>
>> int main()
>> {
>> char arr[]="cool_idea";
>> foo(arr); // prints 9
>>
>
Why not 10? It's a const char[10] because it's a null terminated string.


>> 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,
>

We can do that today with templates. You're trying to make non-templates
behave like templates, and what you want is impossible in separately
compiled code.


> 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.
>

No you don't. If your 'foo' function is defined in a separate source file
there is no way for it to get the information you want. It's impossible.



>
>
>> 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.
>

It's not needed.


> 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
>> parameters
>>
>> Yet, 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?
>>
>> Regards
>> Nadir
>>
>>
>> Sent 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 12:04:38