Doubt const to create template

1

I'm with c ++ learning the templates, but I have a doubt, this code to create templates I understand what it does:

template <typename T> T max(T a, T b) { return a < b ? b : a; }

But they say that if I want to declare the template as inline + using references , then it's like this:

template <typename T> inline
const T& max (const T& a, const T& b) { return a < b ? b : a; }

Then I do not understand two things:

The first T& max : means that the function returns a value of type T by reference? And another thing I do not understand is the const T& max : how does the const work? What does it mean for what it returns?

    
asked by ProgrammerJr 28.10.2017 в 02:22
source

2 answers

2

I think that your two doubts do not have to do specifically with template functions but with functions in general, their types of return and types of parameters. Then, if that function is or is not a template, it does not change those concepts.

  

The first T & max: means that the function returns a value of type T   By reference?

max () returns a reference (a particular constant reference.) It does not return "by reference", it returns "a reference".

And with respect to the second one, a function returns a constant reference so that this value can not be modified; otherwise, you could assign the returned value; for example:

int& f(int& i)
{
    i += 10;
    return i;
}

int main()
{
    int a = 5;
    std::cout << a << '\n';

    std::cout << f(a) << '\n'; 

    f(a) = 3;  // no parece muy útil pero va de ejemplo
    std::cout << a << '\n';
}

A frequent case of using functions that return a reference is the operator [] of a class that represents a collection of objects. In this case it is natural to put so much

int n = a[i]; // asigna a n el valor de a[i]

as

a[i] = n; // asigna a a[i] el valor de n
    
answered by 28.10.2017 в 05:22
2

If you declare the template such that:

template <typename T> T max(T a, T b) { return a < b ? b : a; }

The function uses a and b by value, which implies that you will have to make a copy of the parameters to be able to work with them. If you use the function with, for example integers:

int a = 5, b = 10;
std::cout << max(a,b);

However, imagine that instead of a native type you are comparing slightly heavier objects:

struct dato
{
  int* listado; // supongamos que tiene varios millones de elementos
  int tam_listado;

  std::vector<int> otroListado; // supongamos que tiene otros tantos

  // otros miembros
  // ...

  // constructor copia
  dato(dato const& b)
    : listado(new int[b.tam_listado]),
      tam_listado(b.tam_listado),
      otroListado(b.otroListado),
      /* ... */
  {
    for(int i=0; i<tam_listado; i++)
      listado[i] = b.listado[i];
  }

  // para que funcione max()
  bool operator<(dato const& b) const
  { /* ... */ }
};

// para que funcione cout
std::ostream& operator<<(std::ostream& os, dato const& a)
{ /* ... */ }


dato d1, d2;
cout << max(d1,d2);

What happens is that the program now makes two copies of heavy objects to be used locally and that can seriously penalize the performance ... there will also be situations in which the objects will not allow the making of copies ( you can have the copy constructor disabled) ... which will prevent you from directly using the function.

The second version you propose:

template <typename T> inline
const T& max (const T& a, const T& b) { return a < b ? b : a; }

Its main advantage is that it avoids creating local copies, which prevents bottlenecks when using the function with heavy objects. Of course it is a better option than the first although it may slightly penalize the performance with native types (a reference may imply an indirection)

However, the version is far from perfect. In the case of heavy objects, imagine what happens with this instruction:

dato d1, d2;
d1 = max(d1,d2); 

You are making a heavy copy that, with the current configuration, you will not be able to avoid.

Are there better alternatives?

If you move to some non-archaic standard (C ++ 11 onwards), you have a feature called syntax move (there are some entries on this in SOes). This new feature is accompanied by a new constructor and assignment operator:

Clase::Clase(Clase && param);

Clase& Clase::operator=(Clase && param);

This syntax is intended to optimize operations involving heavy objects. The two new functions whose prototype you have seen above this paragraph are invoked in certain situations and allow to move pointers and other internal objects from one instance to another (instead of making a copy of them). I'm not going to expand on this part because that would be another different question.

The fact is that with this in mind it is best to take advantage of this feature directly to design the function:

template <typename T> T max(T && a, T && b) { return a < b ? b : a; }

At first glance it seems to have everything since it uses the syntax move , which is smart enough to become a copy if the type T has disabled that feature ... Or not?

The truth is that the function does not fully meet its mission:

#include <iostream>

template <typename T> T max(T && a, T && b) { return a < b ? b : a; }

struct POO
{
  POO() = default;
  POO(POO &&)
  { std::cout << "move\n"; }
  POO(POO const&)
  { std::cout << "copy\n"; }

  bool operator<(POO const&) const
  { return true; }
};

std::ostream& operator<<(std::ostream& os, POO const&)
{
  return os << 1;
}

int main()
{
  POO a, b;
  POO c = max(a,b);
  POO d = max(std::move(a),std::move(b));
  std::cout << c << d; // Para evitar warnings por variables sin uso
}

The program will show the following result:

copy
copy
11

When it would have been expected,

copy
move
11

What happened?

Well, in order not to get involved with an excess of theory we can leave it in that the compiler needs a little help to correctly solve the return of the function. That little help is called perfect forwarding and, applying it, the program would look like this:

template <typename T> T max(T && a, T && b)
{ return a < b ? std::forward<T>(b) : std::forward<T>(a); }

If we now try the example, the program returns the following result:

copy
move
11

With what, now, we have a function that adapts perfectly to all possible uses

And now, answering other questions ...

  

The first T & max: means that the function returns a value of type T, by reference?

What it means is that it will return a reference to a value ... and that value will be of type T.

Understanding the references a little with an example:

int  a = 5;
int  b = a; // copia
int& c = a; // referencia

b = 6; // solo afecta a b
c = 7; // al ser una referencia, a tambien cambiará

std::cout << a << b << c; // Imprime 767
  

how does the const act?

Using const prevents modifications to the parameters. It is a protection mechanism that indicates that the parameters a and b are not going to be modified in any way after calling the function.

On the other hand, const is necessary in this case because otherwise you could not do this:

std::cout << max(5,6);

Since those numbers written in fire in the code can not be managed with non-constant references ... What happens if you try to modify the value through the reference?

  

What does it mean for what it returns?

The function receives two constant references, then the only thing it could return is either a value T (which implies copies) or a constant reference (to respect the types).

Returning a constant reference allows you to avoid making copies in certain circumstances:

int c  = max(a,b); // se hace una copia
int& d = max(a,b); // No hace copia
    
answered by 30.10.2017 в 09:29