Templates for different data in C ++

6

I'm using templates in C ++ and I'm new to this, so I do not quite understand how to "reuse" functions regardless of the type of data.

I have the code shown below:

template<typename T>
const T &min(const T &a, const T &b)
{
  if ( a < b )
        return a;
  else  return b;
}

Thanks to this code it is possible to use it for different data types:

int main()
{
    cout << min( 7, 8 ) << endl;
    cout << min( string( "hola" ), string( "adiós") ) << endl;
}

However, the data types have to be the same ... How would you have to change the code so that the following would work (gives an error when compiling)?

int main()
{
    cout << min( 7, string( "hola" ) ) << endl;
}
    
asked by adamista 25.05.2018 в 12:43
source

2 answers

5

In order for a template ( template ) to receive two different types of data, you must indicate that it can work with two different types:

template <typename primer_tipo, typename segundo_tipo>
???? min(const primer_tipo &p, const segundo_tipo &s)
{
    if ( p < s )
        return p;
    else  return s;
}

But this brings us a problem, which of the two types should return the function? The obvious answer is " the type of value that was less ", but there is no mechanism in C ++ to make a function return two different types.

However we can use a std::variant :

template <typename primer_tipo, typename segundo_tipo>
std::variant<primer_tipo, segundo_tipo> min(const primer_tipo &p, const segundo_tipo &s)
{
    if ( p < s )
        return p;
    else  return s;
}

The std::variant is a type of data that can contain one of the data of the type with which it is defined. But that does not mean that the code will work, it means that the min function can accept two different types. If we make the call you propose:

min( 7, string( "hola" ) )

The code will not compile because there is no operator smaller than ( < ) that compare integers with strings, luckily we can define one:

bool operator<(const int &i, const std::string &s)
{
    return i < s.length();
}

In this way, the following code:

auto m1 = min(7, std::string{"hola"});
auto m2 = min(10, std::string{"patatas fritas con ketchup y mayonesa"});

In m1 we will have "hola" and in m2 we will have 10 . You can see the code working in Wandbox 三 へ (へ ਊ) へ ハ ッ ハ ッ .

  

The operator you define is made to operate with data of int and another string, but how could it be done for two types int , for two types string ? Should we repeat the code for each one or can it be done in a generic way?

You can not overload operators for base types so you can not create a comparison operator for two int , as for the comparison operator of two string , this already exists . If you want to generalize the comparison " scalar value " against " string " we must use templates again:

template <typename escalar>
bool operator<(const escalar &e, const std::string &s)
{
    static_assert(is_scalar_v<escalar>, "Debe ser un escalar!");
    const auto length = s.length();
    return static_cast<decltype(length)>(e) < length;
}

The previous operator will work whenever you compare a scalar (numbers) against a std::string , but it only works with one type of character string, we can go one step further:

template <typename escalar, typename caracter>
bool operator<(const escalar &e, const std::basic_string<caracter> &s)
{
    static_assert(std::is_scalar_v<escalar>, "Debe ser un escalar!");
    const auto length = s.length();
    return static_cast<decltype(length)>(e) < length;
}

With this change you can compare any scale with any type of chain ( std::string , std::wstring , std::u16string , ...), if you compare a non-scalar data with a string it will give a compilation failure with the message :

static assertion failed: Debe ser un escalar!

You can see the code working in Wandbox 三 へ (へ ਊ) へ ハ ッ ハ ッ .

    
answered by 25.05.2018 / 13:11
source
4
  

How would you have to change the code so that the following would work (gives an error when compiling)?

If you only define a type in the template (in this case T ):

template<typename T> 
const T &min(const T &a, const T &b)
{
  if ( a < b )
        return a;
  else  return b;
}

That T can only be replaced by a single type, such as int :

const int &min(const int &a, const int &b)
{
  if ( a < b )
        return a;
  else  return b;
}

If you need the function to use two different types, you must declare two different types:

template<class T, class U> 
const /* ¿? */ &min(const T &a, const U &b)
{
  return (a < b)? a : b;
}

Now, for you to compile correctly you just need to have a comparison operator < between the two types you are going to use ... But here we have another problem and it is ... what do we return here? I have assumed that the type T is returned but ... What happens if you need to return b that is of type U ? What if it turns out that you can not convert U to T ?

In the case of the return type there is no good solution and, regret to confirm, the type of return has to be determined at compile time and you need that type to be determined at run time.

One possibility ... if you can work with C ++ 17 is that you use std::variant :

template<class T, class U>
std::variant<T,U> Min(T const& a, U const& b)
{
  return (a < b)? a : b;
}

int main()
{
  for( int i=4; i<6; i++ )
  {
    auto resultado = Min(i,4.5);

    if( auto intPtr = std::get_if<int>(&resultado) )
    {
      std::cout << *intPtr << '\n';
    }
    else
    {
      auto num = std::get<double>(resultado);
      std::cout << num << '\n';
    }
  }
}

But we already see that its use is not too friendly ... but it works.

  

how could the operator be created < so that it takes into account all types of data (class T, class U ...)?

Operator overload < does not involve too much mystery:

bool operator<(int a,std::string const& b)
{
  return a < b.length();
}

template<class T, class U>
std::variant<T,U> Min(T const& a, U const& b)
{
  if( a < b)
    return a;
  return b;
}

int main()
{
  for( int i=4; i<6; i++ )
  {
    auto resultado = Min(i,std::string("abcde"));

    if( auto intPtr = std::get_if<int>(&resultado) )
    {
      std::cout << *intPtr << '\n';
    }
    else
    {
      auto num = std::get<std::string>(resultado);
      std::cout << num << '\n';
    }
  }
}
    
answered by 25.05.2018 в 12:47