C
C#14mo ago
substitute

❔ Is it possible to use a generic type as another type outside of the generic class w/o reflection?

Hi, in C++ we can write code like the following:
#include<iostream>

class test1 {}; //example, can be anything.
class test2 {}; //example, can be anything.
class test3 {}; //example, can be anything.

template <typename T1, typename T2, typename T3>
class tuple
{
public:
using First = T1;
using Second = T2;
using Third = T3;
};

using my_tuple = tuple<test1, test2, test3>; //Alias tuple<i,j,k>

int main(){
my_tuple::First first_ele; //Variable of test1 derived from T1 on my_tuple alias
my_tuple::Second second_ele; //Variable of test2 derived from T2 on my_tuple alias
my_tuple::Third third_ele; //Variable of test3 derived from T3 on my_tuple alias

printf("%s\n", typeid(first_ele).name()); //prints the type test1
printf("%s\n", typeid(second_ele).name()); //prints the type test2
printf("%s\n", typeid(third_ele).name()); //prints the type test3
}
#include<iostream>

class test1 {}; //example, can be anything.
class test2 {}; //example, can be anything.
class test3 {}; //example, can be anything.

template <typename T1, typename T2, typename T3>
class tuple
{
public:
using First = T1;
using Second = T2;
using Third = T3;
};

using my_tuple = tuple<test1, test2, test3>; //Alias tuple<i,j,k>

int main(){
my_tuple::First first_ele; //Variable of test1 derived from T1 on my_tuple alias
my_tuple::Second second_ele; //Variable of test2 derived from T2 on my_tuple alias
my_tuple::Third third_ele; //Variable of test3 derived from T3 on my_tuple alias

printf("%s\n", typeid(first_ele).name()); //prints the type test1
printf("%s\n", typeid(second_ele).name()); //prints the type test2
printf("%s\n", typeid(third_ele).name()); //prints the type test3
}
This effectively allows a special type-of-types to exist that only exists as a way of tying together types into a singular type (tuple<i,j,k>) while allowing access to the inner types i,j,k at compile time. I was wondering if there was an equivalent in C# that didn't require reflection. In essence, I would like to do something like this
using CreateAccount = ReqResErr<Request.CreateAccount,Response.CreateAccount,Error.CreateAccount>
using CreateAccount = ReqResErr<Request.CreateAccount,Response.CreateAccount,Error.CreateAccount>
to later consume like
Envelope.Open<CreateAccount>
Envelope.Open<CreateAccount>
which does like
T1 Req = T0::First
T2 Res = T0::Second
T3 Err = T0::Third
T1 Req = T0::First
T2 Res = T0::Second
T3 Err = T0::Third
Thanks!
72 Replies
reflectronic
reflectronic14mo ago
there is no way to do this without making Open have three generic type parameters like void Open<TReq, TRes, TErr>(...)
substitute
substituteOP14mo ago
Yes, I understand that I can pass in three distinct type parameters, and I plan on having an overload for that the problem is, I would like to create a type alias of the three type parameters
reflectronic
reflectronic14mo ago
you can't. you would need something like https://github.com/dotnet/csharplang/issues/5556 for that
GitHub
[Proposal]: Practical existential types for interfaces · Issue #555...
Practical existential types for interfaces Proposed Prototype: Not Started Implementation: Not Started Specification: Not Started Intro Previously I proposed adding some form of existential types t...
substitute
substituteOP14mo ago
such that
using CompoundType = TypeTuple<Type1, Type2, Type3>
using CompoundType = TypeTuple<Type1, Type2, Type3>
which would be consumed like
BaseType SendRequest<TypeTuple<Type1, Type2, Type3>>(Type1 request) where Type2 : BaseType where Type3 : BaseType
{
return SendRequest<Type1, Type2, Type3>(request);
}

BaseType SendRequest<T,J,K>(T request) where J : BaseType where K : BaseType
{
//if success, return J, if err return K
}
BaseType SendRequest<TypeTuple<Type1, Type2, Type3>>(Type1 request) where Type2 : BaseType where Type3 : BaseType
{
return SendRequest<Type1, Type2, Type3>(request);
}

BaseType SendRequest<T,J,K>(T request) where J : BaseType where K : BaseType
{
//if success, return J, if err return K
}
SendRequest<CompoundType>(req);
SendRequest<CompoundType>(req);
Yes, that's exactly what I'd like shame
Florian Voß
Florian Voß14mo ago
I'm not sure but I think that wouldn't be called an overload would it? Other generic typeparams / more generic typepaprams is a significant difference in the method signature like DoSomething<T1, T2> is cannot be an overload of DoSomething<T>. Those are seperate functions, each possibly having their own overloads. Only parameter list is allowed to be different for overloads
substitute
substituteOP14mo ago
You are misunderstanding https://godbolt.org/z/ezGGhnfG7 Here’s a contrived example in C++
Florian Voß
Florian Voß14mo ago
i don't know c++
substitute
substituteOP14mo ago
The existential types would solve the problem if they weren’t just a proposal.
Florian Voß
Florian Voß14mo ago
enlighten me
substitute
substituteOP14mo ago
Sure. I’ll write a worked out example when I am home. Alright, adapting the C++ to pseudo-C#
class test1 {};
class test2 {};
class generic_error {};
class special_error {};

class tuple<T1, T2, T3>
{
using First = T1; //existential type
using Second = T2; //existential type
using Third = T3; //existential type
};


using my_tuple<T1, T2> = tuple<T1, T2, generic_error>; //type alias

using my_tuple_special<T1, T2> = tuple<T1, T2, special_error>; //type alias

void my_func<T,J,K>()
{
//do something with T, J, and K
}
void my_func<T>()
{
my_func<typename X::First, typename X::Second, typename X::Third>();
}

int main(){
my_func<my_tuple<test1,test2>>();
my_func<my_tuple_special<test1, test2>>();
}
class test1 {};
class test2 {};
class generic_error {};
class special_error {};

class tuple<T1, T2, T3>
{
using First = T1; //existential type
using Second = T2; //existential type
using Third = T3; //existential type
};


using my_tuple<T1, T2> = tuple<T1, T2, generic_error>; //type alias

using my_tuple_special<T1, T2> = tuple<T1, T2, special_error>; //type alias

void my_func<T,J,K>()
{
//do something with T, J, and K
}
void my_func<T>()
{
my_func<typename X::First, typename X::Second, typename X::Third>();
}

int main(){
my_func<my_tuple<test1,test2>>();
my_func<my_tuple_special<test1, test2>>();
}
the call of my_func<> is using my_func<T> and passing either my_tuple<T1, T2> or my_tuple_special<T1, T2> those two aliases accept two type parameters, and have a specified third parameter (generic_error, and special_error) the my_func<T> calls my_func<T,J,K> by pulling the three types off of the tuple type. effectively,
my_func<my_tuple<test1,test2>>();
my_func<my_tuple<test1,test2>>();
evaluates to
my_func<test1, test2, generic_error>()
my_func<test1, test2, generic_error>()
and
my_func<my_tuple_special<test1, test2>>();
my_func<my_tuple_special<test1, test2>>();
would evaluate to
my_func<test1, test2, special_error>>();
my_func<test1, test2, special_error>>();
this allows for a tight coupling at the compiler level of things that should exist together in general cases, while enabling the user to supply their own types for T,J,K in my_func<> if they so desire. in my use-case, it would be for creating basic type mappings for api calls that aren't mapped to a specific underlying type yet (or use the same type for one of the types.) such like
using GenericApiError<Req,Res> = tuple<Req,Res,GenericError>;

using CreateAccount = GenericApiError<CreateReq, CreateRes>;
using DeleteAccount = GenericApiError<DeleteReq, DeleteRes>;
using UpdateAccount = GenericApiError<UpdateReq, UpdateRes>;

Send<CreateAccount>(create_data);
Send<UpdateAccount>(update_data);
Send<DeleteAccount>(delete_data);

Send<OtherReq,OtherRes,OtherError>(other_data);
using GenericApiError<Req,Res> = tuple<Req,Res,GenericError>;

using CreateAccount = GenericApiError<CreateReq, CreateRes>;
using DeleteAccount = GenericApiError<DeleteReq, DeleteRes>;
using UpdateAccount = GenericApiError<UpdateReq, UpdateRes>;

Send<CreateAccount>(create_data);
Send<UpdateAccount>(update_data);
Send<DeleteAccount>(delete_data);

Send<OtherReq,OtherRes,OtherError>(other_data);
also, it would be overloading. The functions have the same names, but different (generic) parameters.
my_func<T1, T2, ..., Tn>(A1, A2, ..., An)
my_func<T1, T2, ..., Tn>(A1, A2, ..., An)
each T and A is a parameter in the function. One is a runtime construct (Args) and one is a compile time construct (Generic) if it wasn't, stuff like this wouldn't be legal
using System;

public class Program
{
public static void test<T>() {}

public static void Main()
{
test<int>();
test<float>();
Console.WriteLine("Hello World");
}
}
using System;

public class Program
{
public static void test<T>() {}

public static void Main()
{
test<int>();
test<float>();
Console.WriteLine("Hello World");
}
}
(it is) there is nothing in the parameter list (...) to distinguish each call to test() (and both return void), instead the compile time type info passed into the generic parameter is used. one is test<int> and the other is test<float>
Florian Voß
Florian Voß14mo ago
pretty sure that isn't true 🤷‍♂️ Something<T> is a totally different class than Something and same goes for methods
substitute
substituteOP14mo ago
Method overloading is literally when two methods have the exact same name but different parameters that's it.
Florian Voß
Florian Voß14mo ago
yes but its about the params list
substitute
substituteOP14mo ago
my_func is a method
Florian Voß
Florian Voß14mo ago
params[] not the generics
substitute
substituteOP14mo ago
no, it is not different return types are function overloading
Florian Voß
Florian Voß14mo ago
thats wrong too
substitute
substituteOP14mo ago
int my_func()
float my_func()

int x = my_func()
float y = my_func()
int my_func()
float my_func()

int x = my_func()
float y = my_func()
Florian Voß
Florian Voß14mo ago
those are not overloads check your facts overload requries same method signature which includes the returntype as well as any generic typeparams
substitute
substituteOP14mo ago
In some programming languages, function overloading or method overloading is the ability to create multiple functions of the same name with different implementations. Calls to an overloaded function will run a specific implementation of that function appropriate to the context of the call, allowing one function call to perform different tasks depending on context.
whatever arbitrarty restrictions Microsoft added to C# is their own issue. fundamentally it is still function overloading
Florian Voß
Florian Voß14mo ago
dunno where you got this from but this explains overriding, not overloading
substitute
substituteOP14mo ago
no, it is literally overloading https://en.wikipedia.org/wiki/Function_overloading Microsoft uses the same verbiage in their C++ docs as well
C++ lets you specify more than one function of the same name in the same scope. These functions are called overloaded functions, or overloads. Overloaded functions enable you to supply different semantics for a function, depending on the types and number of its arguments.
https://learn.microsoft.com/en-us/cpp/cpp/function-overloading?view=msvc-170 when you use templates, you get template function overloading
Florian Voß
Florian Voß14mo ago
"depending on its types and number of arguments" thats exactly what I just said
substitute
substituteOP14mo ago
which is a subset of function overloading
Florian Voß
Florian Voß14mo ago
its not dependant on returntype
substitute
substituteOP14mo ago
types includes return type
Florian Voß
Florian Voß14mo ago
no the word types refers to the types of the arguments here... either typpe or numbber of args is distinguish
substitute
substituteOP14mo ago
the return type is a type of an argument when you compile down into assembly code
Florian Voß
Florian Voß14mo ago
no its sadge
substitute
substituteOP14mo ago
it absolutely is, there are entire calling convention rules about preserving specific registers for the return value of a routine
SinFluxx
SinFluxx14mo ago
But you're completely ignoring the intent of the wording being quoted
substitute
substituteOP14mo ago
he's also ignoring the definition in Wikipedia which is the general definition without any particular language rules
int operate (int a, int b)
{
return (a*b);
}

double operate (double a, double b)
{
return (a/b);
}
int operate (int a, int b)
{
return (a*b);
}

double operate (double a, double b)
{
return (a/b);
}
this is legal in C++
Florian Voß
Florian Voß14mo ago
and also we're still in c# server. I believe if you if you tell me they are considered overloads in c++ cuz I don't know c++ at all but in c# they're not!
substitute
substituteOP14mo ago
I am using C++ as an example to explain my stance specific restrictions applied to what you can do in some particular language does not change the underlying concept in computer science
Florian Voß
Florian Voß14mo ago
okay but I'm telling you in c# different return type or different generic type means its not an overload but a totally seperate method
substitute
substituteOP14mo ago
you are aware that overloaded methods are all different methods when you hit compile right? how do you think the code actually compiles down?
Florian Voß
Florian Voß14mo ago
and from the definition you just sent i conclude its the same in c++ but lets not discuss that I don't care at all. I know the language I work with and I dont work with IL or assemblers or anything like that
substitute
substituteOP14mo ago
public int X(int x) { }
public float X(float x) { }
public int X(int x) { }
public float X(float x) { }
is also valid, so different return types are absolutely supported in C#
Florian Voß
Florian Voß14mo ago
again, I'm telling you about C# right now
substitute
substituteOP14mo ago
so you are going to tell me that
public int X(int x) { }
public float X(float x) { }
public int X(int x) { }
public float X(float x) { }
X is not an overloaded function?
Florian Voß
Florian Voß14mo ago
correct read docs if you dont believe me
substitute
substituteOP14mo ago
lol
Florian Voß
Florian Voß14mo ago
either read this again or read c# docs, that would make more sense
substitute
substituteOP14mo ago
then
public int X(int x) { }
public int X(float x) { }
public int X(int x) { }
public int X(float x) { }
is also not an overload using your logic.
Florian Voß
Florian Voß14mo ago
wrong, this is an overload are you even reading my messages?!
substitute
substituteOP14mo ago
In some programming languages, function overloading or method overloading is the ability to create multiple functions of the same name with different implementations. Calls to an overloaded function will run a specific implementation of that function appropriate to the context of the call, allowing one function call to perform different tasks depending on context.
Florian Voß
Florian Voß14mo ago
when only number or type of params is different then its an overload
substitute
substituteOP14mo ago
nothing here states as a computer science concept that there is anything required other than two functions having the same name
Florian Voß
Florian Voß14mo ago
I dont agree with wikipedias definition on overloading. Overloaded methods should all share the same implementation. Their definition fits much better for method overriding than overloading
substitute
substituteOP14mo ago
the whole point of function overloading is to have the same function name (symbol) for different implementations as you can't accept different types in the same implementation in a strongly typed language if the types are not of a common base type (and you're taking the base type.)
Florian Voß
Florian Voß14mo ago
no the point is to accept different args xD different implementations would be method overriding or method hiding, two different concepts
substitute
substituteOP14mo ago
method overriding has nothing to do with overloading overriding is for implementing a virtual function in a derived type
Florian Voß
Florian Voß14mo ago
correct I didn't say it would you are litteraly not reading my mesages, lets quit here
substitute
substituteOP14mo ago
the point of overloading is to allow for the same symbol to have more than one implementation you are literally disagreeing with the fundemental concept and applying constraints of a singular language over the concept.
Florian Voß
Florian Voß14mo ago
thats not correct. Your definition from overloads in c++ aligned very well with everythign I've said you just misunderstood it as both @SinFluxx and me pointed out pay attention to the wording!
substitute
substituteOP14mo ago
The example from Microsoft doesn't include template functions. The only reason changing only the return type outside of something like a generic/template doesn't work is because it becomes difficult to determine which overload to use based on the input args. template function overloading (or any form of meta-programming) fully allow for overloading of just return types.
Florian Voß
Florian Voß14mo ago
I'm not gonna discuss this further... read about overloads in c#, c++, java or any other langauge you want, they will all tell you what I just told you
substitute
substituteOP14mo ago
as the type parameters (a compile time ONLY construct) are used to determine which overload to use.
You may overload a function template either by a non-template function or by another function template. If you call the name of an overloaded function template, the compiler will try to deduce its template arguments and check its explicitly declared template arguments. If successful, it will instantiate a function template specialization, then add this specialization to the set of candidate functions used in overload resolution. The compiler proceeds with overload resolution, choosing the most appropriate function from the set of candidate functions. Non-template functions take precedence over template functions. The following example describes this:
the only constraint is the either compile time (generic) or runtime (arg) types and the name. or we can read the actual C++ standard
1. When two or more different declarations are specified for a single name in the same scope, that name is said to be overloaded, and the declarations are called overloaded declarations. Only function and function template declarations can be overloaded; variable and type declarations cannot be overloaded
SinFluxx
SinFluxx14mo ago
Why do we keep talking about c++ on a c# server/question?
substitute
substituteOP14mo ago
Because I'm using it as a counter-example to the actual concept of a function overload
Florian Voß
Florian Voß14mo ago
I don't get it either, I tried to tell him we use c# here
substitute
substituteOP14mo ago
the actual fundamental concept not the restrictions that C# places on it.
Florian Voß
Florian Voß14mo ago
its not a language specific restriction I speak 5 languages its the same in all of them
substitute
substituteOP14mo ago
in my example in C# that would be overloading the runtime parameters (args) are not different, but the compile time args (generics) are those (the generics) are part of the function signature
Florian Voß
Florian Voß14mo ago
there is onoly a single method there, whats the overload?
substitute
substituteOP14mo ago
public static void test<T>() {}
public static void test<T>() {}
evaluates to
public static void test() // of int
public static void test() // of float
public static void test() // of int
public static void test() // of float
Florian Voß
Florian Voß14mo ago
there is just Test<T>() I don't see an overload
substitute
substituteOP14mo ago
what do you think happens when the compiler takes the generic? it generates an overload for each unique case of the generic for the function
Florian Voß
Florian Voß14mo ago
.................
substitute
substituteOP14mo ago
each call still has the same name.
Florian Voß
Florian Voß14mo ago
if you want me to have a look I can decompile it to IL and have a look but again its a C# server we talk about c# here this is getting very annoying, please stop pinging me
Accord
Accord14mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server