C# generics vs C++ templates - need a clarification about constraints

前端 未结 5 1007
萌比男神i
萌比男神i 2021-02-14 05:15

Duplicate

What are the differences between Generics in C# and Java… and Templates in C++?


Hi all,

I am experienced C++ p

相关标签:
5条回答
  • 2021-02-14 05:52

    Well, in general, C++ templates and C# generics are similar - compared to Java generics which are completely different, but they have also large differences. Like in C#, there is runtime support by using reflection, getting an object describing the types used to instantiate a generics. C++ doesn't have reflection, and all it does with types is done at compile time.

    The biggest difference between C# generics and C++ templates indeed are that C# generics are better type checked. They are always constrained, in the sense that they don't allow operations that are not stated valid at the time of defining the generics. C#'s chief designer raised as a reason of that the added complexity it would have taken to have implied constraints. I'm not well versed with C#, so i can't talk further here. I'll talk about about how matters are in C++ and how they are going to be improved, so that people don't think C++'s stuff is all wrong.

    In C++, templates are not constrained. If you do an operation, at template definition time it is implied that the operation will succeed at instantiation time. It's not even required to a C++ compiler that the template is syntactically checked for validity. If it contains a syntax error, then that error has to be diagnosed at instantiation. Any diagnose before that is a pure goody of the implementation.

    Those implied constraint have shown to be easy for the template designer in the short term, because they don't have to care about stating the valid operations in their template interface. They put the burden on the user of their template - so the user has to make sure he fulfills all those requirements. Often it happens that the user tries seemingly valid operations but fails, with the compiler giving the user hundreds of lines of error messages about some invalid syntax or not found names. Because the compiler can't know what constraint in particular was violated in the first place, it lists all parts of code paths ever involved around the faulty place and all not even important details, and the user will have to crawl through the horrible error message text.

    That is a fundamental problem, which can be solved by just stating at the interface for a template or generics what properties a type parameter has to have. C#, as far as i know it, can constraint the parameter to implement an interface or inherit a base-class. It solves that on a type-level.

    The C++ committee has long seen there is need to fix these problems, and soon (next year, probably), C++ will have a way to state such explicit constraints too (see time-machine note below), as in the following case.

    template<typename T> requires VariableType<T>
    T f(T a, T b) {
        return a + b; 
    }
    

    The compiler signals an error at that point, because the expression as written is not marked valid by the requirements. This first helps the designer of the template to write more correct code, because the code is type-checked already to some degree (well to what is possible there). The programmer can now state that requirement:

    template<typename T> requires VariableType<T> && HasPlus<T, T>
    T f(T a, T b) {
        return a + b; 
    }
    

    Now, it will compiler. The compiler, by seeing T appearing as the return type, automatically implied that T is copyable, because that use of T appears in the interface, rather than in the templates body. The other requirements were stated using requirement clauses. Now, the user will get a appropriate error message if he uses a type that doesn't have an op+ defined.

    C++1x decouples the requirements from the type. The above works for primitive types aswell as for classes. In this sense, they are more flexible, but quite a bit complex. The rules that state when and when requirements are satisfied are long... You can with the new rules say the following:

    template<typename T> requires MyCuteType<T>
    void f(T t) { *t = 10; }
    

    And then, call f with an int! That would work by just writing a concept map for MyCuteType<int> that teaches the compiler how an int can be dereferenced. It will get quite handy in loops like this:

    for_each(0, 100, doSomething());
    

    Since the programmer can tell the compiler how an int can satisfy the concept of an input iterator, you could actually write such code in C++1x, if you only write the appropriate concept map, which really isn't all that difficult.

    Ok, enough with this. I hope i could show you that having templates constrained is not all that bad, but in fact better, because the relationship betweens types and the operations on them within the templates are now known by the compiler. And i haven't even written about axioms, which are another nice thing in C++1x' concepts. Remember that this is future stuff, it's not yet out, but it will approximately at 2010. Then we will have to wait for some compiler to implement that all :)


    UPDATE FROM "FUTURE"

    C++0x concepts were not accepted into the draft but have been voted out at late of 2009. Too bad! But perhaps we will see it again in the next C++ version? Let's all hope!

    0 讨论(0)
  • 2021-02-14 05:57

    Template and generics are really a different thing. One of the goals of generics is to be able to use them in a cross library, cross language way, which is not the same as C++ templates. They are a CLR concept, not a language concept (although they need language support obviously).

    In C++, templates can be seen in a "macros on steroids" kind of way (no flame please, I know templates are not macros), because you can see them a as a textual expansion which is then compiled. This gives them the ability to use whatever is defined on the template parameter (mainly operators for example) because constraints are imposed by the code using them.

    In .NET, since generics are resolved (instantiated) at runtime, constraint have to be imposed at the definition level so that the compiler can ensure their usage will be valid (which means that you cannot use operators on generics parameters because you cannot indicate a constraint for the existence of an operator).

    As I said, the main point of generics is to be able to create generic dll to be used by other projects. This is why they are different.

    0 讨论(0)
  • 2021-02-14 06:00

    You'll get a better answer shortly, I'm sure. At that time, I'll delete this one.

    The difference is that templates in C++ are similar to macros. It's when the template is instantiated that the code is compiled, and compilation errors are displayed if the implicit constraints are violated. That's how you can do template specializations - the template is basically already expanded by the specialization, so that's the one that's used.

    Generics in .NET (also in VB.NET) are a runtime construct. They're a special kind of type. The constraints are necessary in order to ensure that any actual use of the type will be valid when the type is finally used.

    You can actually use Reflection to look at a generic type and find the type parameters used to instantiate it, or look at a generic definition and see the constraints on each type parameter. In C++, this information is already gone at runtime.

    0 讨论(0)
  • 2021-02-14 06:04

    C++ templates: The compiler checks whether the arguments satisfy the constraints set by the code. For example:

    template <typename T, unsigned int dim>
    class math_vector
    {
        T elements[dim];
    
        math_vector<T,dim> operator+ (const math_vector<T,dim>& other) const
        {
            math_vector<T,dim> result;
            for (unsigned int i = 0; i < dim; ++i)
                result.elements[i] = elements[i] + other.elements[i];
        }
    }
    
    struct employee
    {
        char name[100];
        int age;
        float salary;
    }
    
    math_vector<int, 3> int_vec; //legal
    math_vector<float, 5> float_vec; //legal
    math_vector<employee, 10> employee_vec; //illegal, operator+ not defined for employee
    

    In this example, you could create a class, define operator+ for it and use it as a parameter for math_vector. Therefore, a template parameter is valid if and only if it satisfies the constraints defined by the template's code. This is very flexible, but results in long compilation times (whether a type satisfies the template's constraints must be checked every time the template is instantiated).

    C# generics: Instead of checking the validity of every particular instantiation, which results in longer compile times and is error prone, you declare explicitly that the generic's arguments must implement a particular interface (a set of methods, properties and operators). Inside the generic's code, you can't call any methods freely, but only those supported by that interface. Every time you instantiate a generic, the runtime doesn't have to check whether the argument satisfies a long set of constraints, but only whether it implements the specified interface. Of course, this is less flexible, but it's less error prone, too. Example:

    class SortedList<T> where T : IComparable<T>
    {
        void Add(T i) { /* ... */ }
    }
    
    class A : IComparable<A> { /* ... */ }
    
    class B
    {
        int CompareTo(B b) { /* ... */ }
        bool Equals(B b) { /* ... */ }
    }
    
    SortedList<A> sortedA; // legal
    SortedList<B> sortedB; // illegal
    // B implements the methods and properties defined in IComparable,
    // however, B doesn't explicitly implement IComparable<B>
    
    0 讨论(0)
  • 2021-02-14 06:06

    C# generics are totally different from C++.

    In C#, the compiler basically compiles one class definition for all object types, and a class definition per value type.

    In C++, each type gets its own class definitions.

    The constraints are just for the compiler so you can infer stuff from other locations.

    I'd recommend looking at the Action<T>, Func<T, T> and Predicate<T> delegates and the associated IEnumerable extension methods. Use lambda functions with those and you'll see what the constraints doing.

    0 讨论(0)
提交回复
热议问题