Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: PrimitiveValueType and Generic Pointers #492

Closed
gafter opened this issue Apr 23, 2017 · 7 comments
Closed

Proposal: PrimitiveValueType and Generic Pointers #492

gafter opened this issue Apr 23, 2017 · 7 comments

Comments

@gafter
Copy link
Member

gafter commented Apr 23, 2017

@Ultrahead commented on Thu Apr 23 2015

The idea here is to create a new type, say, "PrimitiveValueType" that would inherit directly from ValueType, and would not allow declaring reference-type field members, only value-type fields’ members that do not hold reference-types. For example:

public primitive Percentage
{}

This would also open the door another proposed feature: Generic Pointers. Take for instance the following operation:

public void DoSomeStuff (TPrimitive* myPrimitivePtr) 
   where TPrimitive: primitive
{}

By redefining types like int, single, double, bool, and so on so forth, so that they become specifications of PrimitiveValueType, fixed-type buferring could be enhanced so that any type defined as primitive is supported. Example:

public class MyClass
{
    public fixed MyPrimitiveType Foo[5]; 
}

For reference, this proposal is related to this discussion on codeplex: http://roslyn.codeplex.com/discussions/543883


@VSadov commented on Thu Apr 23 2015

This seems somewhat similar (if not a duplicate) to the unmanaged generic type constraints. #126
@stephentoub


@zahirtezcan commented on Fri Apr 24 2015

This seems really good for interop purposes. Since primitive types can only contain other primitive types, fixed buffers or pointers: We can use sizeof operator on primitive types, such that size may be known in compile time. For this purpose there should be a way to tell the compiler about packing etc.

If that becomes possible, generic context we will be able to say

fixed (TPrimitive* ptr = &Foo[0])
    ptr->X = 5;

@HaloFour commented on Fri Apr 24 2015

@zahirtezcan Currently you can use StructLayoutAttribute to inform the compiler about the packing (and actual size) of a structure. The runtime defaults to aligning based on the size of the largest element in the structure otherwise.


@zahirtezcan commented on Fri Apr 24 2015

@HaloFour Attributes are for the run-time to determine the size and layout of the structure though. But if compiler can detect size and layout statically; we can use sizeof on primitive structures and that will be whole new level for pointer/interop/memory operations


@HaloFour commented on Fri Apr 24 2015

@zahirtezcan Actually the StructLayoutAttribute never survives to the compiled binary. It exists as a "pseudo-attribute" to give the C# compiler a syntax for specifying the layout and characteristics of the struct without having to define a new syntax. The compiler is, of course, free to calculate that on its own and specify that metadata. It would have to in these cases since the size of the struct would have to be known for fixed sized buffers to be supported since the C# compiler has to account for the total size of the buffer.


@dsaf commented on Mon Apr 27 2015

Could it be used for implementing units of measure? #144


@MikePopoloski commented on Tue Apr 28 2015

+1

Lack of generic pointers is one of my biggest pain points with C#.


@VSadov commented on Tue Apr 28 2015

@MikePopoloski - about generic pointers. I am curious about particular scenarios.
Is that specifically about
-- pointers - as in generalizing byte* to primitive_struct* and use that in unsafe/pinned context,
or about
-- inner references - as in getting a managed reference to an element of T[] and pass that around like described in #118?


@Ultrahead commented on Tue Apr 28 2015

@VSadov: I was migrating the proposal I had submitted to codeplex. Now, for what I can read through #126 and #118, my proposal is not a duplicate since I meant the last two features proposed in my first post here -that is generic pointers and fixed-type buferring- for unsafe contexts.

@zahirtezcan: exactly!

@dsaf: I don't see why not. It would nice to have user-defined literals as in C++11, so you can do something like:

public primitive Radian
{public static Radian operator _rad;

   public static implicit operator Radian(int value)
   {
       return new Radian((value % 360) * Math.PI / 180f);
   }

   public static implicit operator Radian(float f)
   {
       // do the corresponding calculation here ...
   }
   ...
}

So when you use it like var myRadian = 45_rad; or var myRadian = 45.23f_rad;, the literal will pick the corresponding implicit operator to get the Radian;

@MikePopoloski: thanks!


@MikePopoloski commented on Tue Apr 28 2015

@VSadov More of the former than the latter. Things like reading binary files from disk, writing binary blobs to the network, or manipulating native memory blocks allocated for maximum control of cache layout (for example, particle systems). I usually end up using an IL-rewriting process to let me read and write generic pointers from/into a native memory block, since the CLR supports it just fine.

If that, and the calli instruction were exposed to C# code it'd make me super happy. I'd no longer need to run the cumbersome IL rewriting build step.


@Ultrahead commented on Tue Apr 28 2015

@MikePopoloski: "Things like ..." +1 on that


@dsaf commented on Wed Apr 29 2015

Would it lift the unpleasant constants limitation?

https://msdn.microsoft.com/en-us/library/e6w8fe1b.aspx

Constants can be numbers, Boolean values, strings, or a null reference.

I want to be able to pass e.g. a DateTime or a TimeSpan or a Vector2 to a custom attribute, but there is no way to do this at the moment.

Should it be a separate issue or the current one covers it?


@Ultrahead commented on Wed Apr 29 2015

Interesting question, @dsaf .

To be honest, I believe it goes beyond the scope of my suggestion since it would most likely requiere a deeper change in the compiler than adding a "primitive" type.

The thing is that only "simple" built-in types (plus strings) can be declared as constants, leaving out, for this case, user-defined primitives which are initialized at runtime. In order to remove such restriction the compiler should be capable of detecting beforehand which primitives can be deemed as "simple" types, and therefore, that can be assigned at compile time.

And that is a requirement that imvho should be part of a separate issue.


@dsaf commented on Wed Apr 29 2015

I was thinking that this:

... not allow declaring reference-type field members ...

Would lead to this:

... compiler should be capable of detecting beforehand which primitives can be deemed as "simple" types ...

It's interesting that custom attribute constructors accepting non-primitive parameters are allowed to be declared but not to be used :)


@Ultrahead commented on Wed Apr 29 2015

At first, for a moment, I thought the same as you have, but then I realized that there would be more in a type to be considered simple by the compiler than be redefined as a primitive, or otherwise, structs like DateTime, TimeSpan and Vector2 would already be allowed to be declared as constants in the current version of .NET.


@Ultrahead commented on Wed Apr 29 2015

And of course, if I'm wrong, then that would be a welcome feature to have as a result of the proposed primitive type (that is, without the need of submitting a separate issue).


@HaloFour commented on Wed Apr 29 2015

@dsaf

I believe that such changes would require modifications to the CLR, particularly where those constants are to be used with attributes. The limitations on what kinds of values can be encoded and deserialized for attributes are set by the runtime and are a relatively short list of very easy to serialize values, namely integral types, char, enum, bool, string, Type (by way of the full name encoded as string) or arrays of any of those types.

For the most part it seems that this primitive type proposal is compiler candy, a way to explicitly specify that a standard struct is to contain only other value types of a size known at compile-time. That would allow for fixed-size buffers using the compiler trickery currently used. Although the concept of "generic pointers" is an entirely different ballgame.


@Ultrahead commented on Wed Apr 29 2015

Although the concept of "generic pointers" is an entirely different ballgame.

Indeed, but having a primitive type in the language would ease such task if used as a required constraint (that is, not allowing type declared as structs for generic pointers even if they hold no reference types within). So, the only way to have generic pointers would be by the use of primitives.


@Ultrahead commented on Thu Oct 08 2015

This branch could really help to bring PrimitiveValueType base class to life: http://xoofx.com/blog/2015/09/27/struct-inheritance-in-csharp-with-roslyn-and-coreclr/


@HaloFour commented on Thu Oct 08 2015

@Ultrahead I don't see how that branch does anything to make this proposal possible. You still have the problem that dereferencing that pointer requires that the compiler know at compile time the exact offset in bytes of every member or element. The compiler can't know that with generic methods.

As for the branch itself, calling that "struct inheritance" seems like a bit of a stretch. A form of struct composition maybe. The behavior would probably not be what people would expect, especially the slicing for upcasting which makes downcasting impossible.


@gafter commented on Thu Oct 08 2015

@Ultrahead @HaloFour Actually, I like the idea of struct inheritance. The problem we run into is the meaning of interface implementations. You should be able to override the implementation of a method provided by the base struct, but that is incompatible with the tearing you get when you cast to the base struct.


@Ultrahead commented on Fri Oct 09 2015

You still have the problem that dereferencing that pointer requires that the compiler know at compile time the exact offset in bytes of every member or element. The compiler can't know that with generic methods.

@HaloFour: At compile time, the compiler will know the size of each type of primitive. So, couldn't that task be endorsed to the CLR so that it infers it at runtime, instead?


@xoofx commented on Fri Oct 09 2015

We definitely need a way to express/enforce blittable structs (so that if someone add a managed field, we can detect it) and enforce this at generic constraint level, to be able in the end for example, to take a pointer from it (and pass this pointer to a native method then).

As @MikePopoloski mentioned, we are currently workaround it by IL rewriting this, just get an access to this feature which is very annoying (In SharpDX for example, it is all around used, declared in this file, and IL rewrite here, and used for example here)

In the language, it could also be just a System attribute (Blittable), and on the constraint, we could specify constraints on attributes ( where MyType has Blittable(Attribute))


@MikePopoloski commented on Sat Oct 10 2015

Also note that similar things can be done using typed references: link

Of course, this abuses the internal structure of a typed reference, but it does exactly what I want in a very efficient manner.


@xoofx commented on Sun Oct 11 2015

neat! didn't know that _makeref was also working with generics


@nietras commented on Thu Feb 04 2016

I guess my proposal here is a redundant form of this one Unmanaged generic type constraint + generic pointers Note that this proposal however does not require a new type specifier.


@jamesqo commented on Mon May 09 2016

Enthusiastically +1'ing this proposal, having generic pointers would be a very welcome addition to C# for me. A couple of things I'd do differently, though:

  • Make primitive a modifier for the struct keyword, so e.g. you would write

    public primitive struct Percentage { ... }
  • primitive is just a way for the compiler to verify that the type does not contain any reference fields, it doesn't get reflected at all in the emitted IL. This would mean we could work with existing types that don't have the modifier and use them with the primitive constraint:

    public struct Foo { int field; }
    
    DoSomething(new Foo()); // compiles fine
    
    void DoSomething<T>(T thing) where T : primitive {}

    In other words, types that don't declare primitive might be primitive, types that do are always primitive. This means we don't have to change existing code.

  • No PrimitiveValueType base class, since that would probably break things like reflection if you tried to do this

    Console.WriteLine(typeof(int).BaseType); // "System.ValueType" before, "System.PrimitiveValueType" after
@jnm2
Copy link
Contributor

jnm2 commented Apr 24, 2017

Seems very similar to #206 (#187)

@Ultrahead
Copy link

@jamesqo: I like your idea. If I may, I'd add a new feature to the TYPE class: an IsPrimitive getter.

@jnm2, it has a key difference: we're not dealing with an attribute but, in case of jamesqo idea, a keyword that can be enforced at compile time and accross assemblies.

@jnm2
Copy link
Contributor

jnm2 commented Apr 24, 2017

@Ultrahead The proposal I linked is dealing with not an attribute but a keyword that can be enforced at compile time and across assemblies.
https://github.com/jaredpar/csharplang/blob/4daf8b0bce2739dec53bc91074883f9466ad8ebf/proposals/blittable.md#detailed-design

@Ultrahead
Copy link

I'm sorry. You're right. I was watching #187. Then, I'd say that #206 seems very similar to this proposal, though :)

@Thaina
Copy link

Thaina commented Apr 25, 2017

related #144

@Ultrahead
Copy link

Ultrahead commented May 3, 2017

If the blittable proposal was selected as Champion, and if it will also allow the use of generic pointers of blittable structs, then this proposal should be closed. Please consider adding a new getter to the Type and ValueType classes: IsBlittable.

@gafter
Copy link
Member Author

gafter commented Apr 19, 2018

#187 is part of C# 7.3.

@gafter gafter closed this as completed Apr 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants