Tech Off Thread

8 posts

Forum Read Only

This forum has been made read only by the site admins. No new threads or comments can be added.

Why won't this work? (Generics)

Back to Forum: Tech Off
  • User profile image
    BitFlipper

    If I remember correctly, C++ templates are only evaluated when you actually try to use them. So you could have code in a template that can't compile, but you will only know about it if you try to use the template somewhere. I'm not saying this is a good thing, but it does have advantages from what I am seeing right now with how C# generics work.

    Say I have this:

    void Foo(float[] buffer)
    {
        // Process buffer
    }
    
    void Foo(double[] buffer)
    {
        // Process buffer
    }
    
    void Bar<T>(T[] buffer)
    {
        //...
        Foo(buffer);
        //...
    }

    That does not compile with error:

    Argument 1: cannot convert from 'T[]' to 'float[]

    The error is understandable, since T is not constrained to only float or double, and there is no way that I know of to constrain it only to float or double. You can constrain it to struct, but that doesn't help. It would have been helpful if the compiler took into account how you called Bar and only give an error if you try to call with a type that it can't ultimately resolve (like what would happen in the case of a C++ template).

    The point is that it is extremely easy for us to see what should work and what should not work (only float or double would work) , so why can't the compiler be a little bit smarter and figure this out too? Seems it would be really helpful in some cases.

    Or is there another way to refactor this code so that it does work? Obviously this example is very simple, but in my code, Bar is quite complex and I don't want to rewrite the same method multiple times for each type of buffer I am going to support. Yes inside Bar I can check the types manually (if T is float) and cast to each type when calling Foo but it doesn't seem very elegant. 

  • User profile image
    Sven Groot

    I'm afraid this isn't possible. It works in C++ because templates are not compiled until they are instantiated, at which point the type is known (which is why it's not possible to put template classes in e.g. a DLL, because the compiler must have the template's source available at the point of instantiation (ignoring export templates which almost no compiler supports anyway)).

    .Net generics are compiled without instantiation, so the compiler knows nothing about the type beyond the constraints you've given it. Although it might be possible in cases like this for the compiler to infer the necessary constraints, there would need to be some way to represent those constraints in the metadata (because "float or double" isn't expressable in the current constraint system) so that the compiler knows what's valid when you call the function. I suspect that the rules for something like this would end up being very subtle, where a slight change in the code suddenly changes what types are valid for a generic parameter (and since that's a breaking change for a library, that's not a good thing).

    Unfortunately, you'll have to find another solution.

    EDIT: Another problem with this is how to represent the call to Foo in the IL. A method call in IL always specifies exactly which overload to use, the resolution is done by the C# (or VB, or whatever) compiler. To support this scenario, you'd have to somehow leave the unresolved call in the IL and defer resolution until instantiation.

  • User profile image
    W3bbo

    Although it might be possible in cases like this for the compiler to infer the necessary constraints, there would need to be some way to represent those constraints in the metadata (because "float or double" isn't expressable in the current constraint system) so that the compiler knows what's valid when you call the function.

    In a case like that then you would have to hardcode an implementation for the main value types, but given there's such a small number of them (byte, sbyte, int16, uint16, int32, uint32, int64, uint64, float, double, and decimal) I don't think it's that big a burden to hardcode the special cases for those.

  • User profile image
    BitFlipper

    @Sven Groot:

    Yep, I get what you are saying. In the case of .Net there is much more going on than there is with C++ templates. I think the easiest solution for me would be to explicitly check the type inside Bar and cast to each of the supported Foo overloads (and throw an exception for unsupported types). I guess that isn't so bad now that I think about it.

  • User profile image
    raptor3676

    This remember that at the time of the .net 2.0 beta, there was a lot of fuzz for not having an IArithmetic interface to allow such things...

    Check this article, http://www.codeproject.com/KB/cs/genericnumerics.aspx

    There was even a complaint filed on connect.microsoft.com, but I couldn't find it

  • User profile image
    spivonious

    @BitFlipper: Rather than using generics here, I'd make an IBuffer interface and then create types for FloatBuffer and DoubleBuffer. Then Bar's parameter can become IBuffer.

  • User profile image
    BitFlipper

    , spivonious wrote

    @BitFlipper: Rather than using generics here, I'd make an IBuffer interface and then create types for FloatBuffer and DoubleBuffer. Then Bar's parameter can become IBuffer.

    I am already doing exactly that. I have an AudioBuffer class that encapsulates all supported managed audio buffer formats (float, double, Int16, Int32). The problem is that at some point you have to deal with the internal buffers directly.

    At the point in the code that I mention in the OP, the internal buffer is being manupilated directly and Bar determines what format the output driver requires its data in. Foo in this case converts from the managed audio format into the unmanaged format that the unmanaged driver requires. It has overloads that take differenct managed buffer formats and convert to different unmanaged formats.

    There are more that 20 different native buffer formats so you can see that the the number of overloads will be quite large even if I just support 4 managed buffer formats (about ~80 overloads but fortunately I can ignore some of them).

  • User profile image
    teslaBytes

    Create a new blob obj, and handle your constrant problem by reinventing the stream.

Conversation locked

This conversation has been locked by the site admins. No new comments can be made.