Hacker News new | past | comments | ask | show | jobs | submit login

Interesting, I've never heard of CRTP and will need to read up on it.

I do "get it" that templates are not generics... there's just a lot of gotchas for someone assuming generics, err - templates are similar to other languages. Like the difficulty in nesting templates in templates...




Even in the use of "containers", generics are the complete opposite of how to use templates.

Java / C#'s concept of "Everything derives from Object" is outright rejected in C++. Templates are part of this culture: how can we provide extendibility when nothing is an Object?

Well, look at iterators in C++. All C++ iterators extend from all collections WITHOUT the use of OOP-derivations. That's thanks to the magic of Templates.

------

What's going on? C++ templates allow the creation of new iterator classes. It invokes the compile-time language to create a new class at compile time.

In Java / C#: vector<MyClass> is the same old vector as all other vectors.

In C++: vector<MyClass> is a custom-made, completely separate class from all other vectors. vector<MyClass>::iterator is a 2nd, custom-made completely separate class from all other iterators.

And yet, all of these different classes can be used the same, thanks to the use of auto, or other compile-time structures in C++.


Getting Java's generics out of your head when doing C++ is definitely a challenge. It's just so disappointing you cannot do something like:

    void myFunc(List<? extends MyDataContainer<?>> list);
Instead you end up with a base class MyDataContiner and then manually extending it with something like MyDataContinerInt or MyDataContinerLong etc...

C++ templates may be very powerful, but limitations like this feel out of place in a language as bloated as C++ already is...

Admittedly, I still have much to learn about C++. It's been quite an experience, frustrating at times, learning this language.


I'm curious: what's wrong with myFunc(List<BaseClass * >) ??

EDIT: Remember, in C++, "BaseClass" cannot polymorph, only "BaseClass * " can. (BaseClass is where the data is stored precisely. If its 20 bytes of RAM, then DerivedClass might be 24 bytes of RAM, and therefore can't fit. But both BaseClass * and DerivedClass * are compatible with each other, and may convert into each other, as long as you know the pointer goes to the right kind of class)

And if that's not sufficient, then a static_assert over the type information is probably what you need. Ex:

   template<class T>
   void myFunc(List<T> list){
      static_assert(std::is_nothrow_convertible(T, BaseClass)); // I haven't tried, but this probably works
   }
That's what I mean about C++ templates: they're a meta-language. You can add if-statements / else statements over type information at compile-time in C++.

In this case, we've created a static_assert (aka: a compile-time error will pop up) if T does not convert into BaseClass (note: T may have a CopyConstructor(BaseClass), so maybe its not exactly tthe same concept as what you're trying to do... but this gives you an idea of C++isms...)


> I'm curious: what's wrong with myFunc(List<BaseClass * >) ??

Well, nothing I suppose, except BaseClass cannot then be a template, and you cannot have derived class-specific return types like a template would allow.

So, having BaseClassInt type return int from some function but BaseClassLong return a long gets more challenging than it should be. (I recognize all my examples so far had a void return type, that was just for simplicity).

> But both BaseClass * and DerivedClass * are compatible with each other, and may convert into each other, as long as you know the pointer goes to the right kind of class)

Which loses type safety, unfortunately.

> In this case, we've created a static_assert (aka: a compile-time error will pop up) if T does not convert into BaseClass

I'll look into the static_assert() option, could prove useful in some cases.

At the end of the day, there are two things at play here - my lack of C++ familiarity, and in some cases it's just how C++ is.

It is a strange feeling though, having a language limit your expressiveness for the first time...


> Well, nothing I suppose, except BaseClass cannot then be a template

    template <class T>
    void myFunc(List<T*>){
        static_assert(stuff about T that you want to guarantee at compile time); 
    }
See here: https://en.cppreference.com/w/cpp/header/type_traits

For a list of common functions used for compile-time type checking.


Thank you for this conversation. You've given me some homework to read up on, and I appreciate it a lot!


I think you can get something similar now with C++20 concepts?

    template<typename T, typename U>
    void my_func(std::vector<T>* vec) requires std::derived_from<T, my_data_container<U>>;
(Probably mangled something, but the idea is there)

Without concepts, you're left with SINFAE:

    template<typename T, typename U, typename = std::enable_if_t<std::is_base_of_v<my_data_container<U>, T>>>
    void my_func(std::vector<T>* vec);
or just a regular function and a hope that anything wrong will be caught by the compiler:

    template<typename T, typename U>
    void my_func(std::vector<T>* vec) {
        // use my_data_container<U> somewhere and hope things blow up if something is wrong
        // Not 100% sure this is allowed, but something similar should be at least
    }


Templates are the killer feature of C++. They add incredible power to the language, but also dramatically increase its complexity. Templates are only superficially similar to generics in C#/Java. They are essentially a type-safe, Turing-complete, syntactically-constrained macro system deeply embedded into the grammar of the language.

Few other programming languages offer a similar feature. C++ pulled it off with an ISO standard and multiple conformant implementations.

Templates are loved by many, but they are a contender (along with UB) for the most hated C++ feature due to complexity.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: