user7982333
user7982333

Reputation:

Why not always use templates instead of actual types?

Why not just use templates instead of actual type? I mean, then you would not have to care about what type you are dealing with at any time, right? Or am I wrong and is there actually a reason why we use actual types, like int and char?

Thank you!

Upvotes: 1

Views: 173

Answers (3)

Persixty
Persixty

Reputation: 8589

I think it's an issue of over complication that will never give a benefit.

Consider a simple class:

class Row {
    size_t len;
    size_t cap;
    int* values;
};

NB: You'd really instantiate std::vector<int> but lets look at this as a familiar example...

So looking that way we certainly gain a benefit here by making this a template in the type of values.

template<typename VALUE>
class Row {
    size_t len;
    size_t cap;
    VALUE* values;
};

That's a big win! We can write a general purpose Row class and even if this is part of (say) a maths package and this is a vector space tuple with members like sum() and max() and so on we can use other arithmetic types like long and double and build a very useful template.

How about going further? Why not parameterize the len and cap members?

template<typename VALUE,typename SIZE>
class Row {
    SIZE len;
    SIZE cap;
    VALUE* values;
};

What have we won? Not so much it seems. The purpose of size_t is to be the suitable type to represent object sizes. You could use int or unsigned or whatever but you're not going to gain flexibility (negative lengths won't make sense) and all you will do is arbitrarily limit the size of a row.

Remember to follow this through every single use of Row must be a template and accept an alternative for SIZE. Here's our Matrix template:

template<typename VALUE, typename ROW_SIZE, typename COL_SIZE>
class {
    Row< Row<VALUE,ROW_SIZE> , COL_SIZE> rows;
}; 

OK so we can simplify by making ROW_SIZE the same type as COL_SIZE but ultimately we've done that by picking size_t as the common denominator of sizes.

We can take this to it's logical conclusion and the entry point of the program will become:

int main() {
    main<VALUE,SIZE,/*... many many types ...*/,INDEX_TYPE>();
    return EXIT_SUCCESS;
}

Where every type decision is a parameter and been threaded up through all the functions and classes to the entry point.

There are a number of problems with this:

  1. It's a maintenance nightmare. You can't change or add to a buried class without threading its type decisions up to the entry point.

  2. It will be a compilation nightmare. C++ isn't fast at compiling and this will make it a shed load worse. For a large program I can imagine you might even run out of memory as the compiler resolves the mother of all templates. [more of an issue on larger applications]

  3. Incomprehensible error messages. For good reason compilers struggle to provide easy to trace errors in templates. With templates nested in templates to who-knows how deep that would be a real problem.

  4. You won't gain any useful flexibility. The types are eventually interlinked that many sundry types have a good provided answer that you won't want to change anyway.

In the end if you do have a type that you think is an application parameter (such as value-type in some mathematical package) the best way to parameterize is to use a typedef. typedef double real_type in effect makes the whole source code a template without all that template gubbins all round the shop.

You can typedef float real_type or typedef Rational real_type (where Rational is some imagined rational number implementation) and genuinely create a flexible parameterized library.

But even then you probably won't typedef size_t size_type or whatever because you're not expecting to vary that type.

So in summary you'll end up doing a lot of work to provide flexibility much of which you won't use and have mechanisms such as library level typedef that allow you to parameterize your application in far less conspicuous and labour intensive ways.

I'd say a draft guideline for templates is "Do you need two of them?". If some function or class is likely to have instances with different parameters then the answer is templates. If you think you've got a type (or a value) that is fixed for a given instance of the application then you should use compile time constants and library level typedefs.

Upvotes: 1

user2512323
user2512323

Reputation:

One reason is that templates need to be instantiated for every concrete type, so, lets say if you have a function like that:

void f(SomeObject object, Int x){
  object.do_thing_a(x);
  object.do_thing_b(x);
}

And Int is templated, compiler must generate one instance of foo, do_thing_a, do_thing_b, and probably many more functions called from do_thing_a and do_thing_b for every Int be it short or unsigned long long. Sometimes this can even lead to a combinatorial explosion of instances.

Also, you can't make virtual member functions template, for obvious reasons. There is now way compiler can know what instances it should put into vtable prior to compiling the whole program.

By the way, functional languages with type inference doing this all the time. When you write

f x y = x + y 

in Haskell, you actually get (very loosely speaking) something close to C++

template<class Num, class A>
A f(A x, A y){
    return Num::Add(x, y);
}

Yet in Haskell the compiler is not obligated to generate an instance for every concrete A.

Upvotes: 0

Panos
Panos

Reputation: 1834

There are a couple of reasons. Some of them I shall now list:

Backwards compatibility. Some code bases don't use templates and so you can just replace all the code.

Errors in Code. Sometimes you want to be certain that you are getting a float/int/char or what have you in order for your code to run without errors. Now it would be a fair assumption to use templates and then cast the types back to what you need but tat doesn't always work. For example:

#include <iostream>
#include <string>

using namespace std;

void hello(string msg){
    msg += "!!!";
    std::cout << msg << '\n';
}

int main(){
    hello("Hi there"); // prints "Hi there!!!"
}

This works. But replacing the function above with this one doesn't work:

template<typename T>
void hello(T msg){
    msg += "!!!";
    std::cout << msg << '\n';
}

(Note: Some compilers may actually run the code above but usually you should get an error in evaluation of 'operator+=(const char*, char [4])')

Now there are way to get around such errors but sometimes you just want a simple working solution.

Upvotes: 0

Related Questions