Choosing between template function and auto type inference

I have a general question about template functions and aut type inference for functions.

For years, we could write a template function:

template <class T> T add(T a,T b){
    return a+b;
}

      

There is TS for using auto to output function parameters

auto add(auto a,auto b){
    return a+b;
}

      

I, though auto, had no way to get to the actual type and used static elements for example, but this works great:

#include <iostream>

struct foo
{
    static void bar(){std::cout<<"bar"<<std::endl;}
    static int i ;
};
int foo::i{0};
void t(auto f){
    decltype(f)::bar();
    std::cout<<    decltype(f)::i<<std::endl;
}
int main(int argc, char *argv[])
{
    t(foo());
    return 0;
}    

      

So is there any reason to choose one over the other?

+3


source to share


3 answers


The obvious reason for this particular code would be that they really don't have the same semantics at all.

In particular, if you pass arguments of different types, the using version auto

will infer the type independently for each and then infer the type of the result based on that.

Conversely, with the template version, you have specified exactly one type, so the arguments must be of the same type (and the result will be the same).



So, to make the code more nearly equivalent, you really need to write the template more:

template <class T, class U> 
auto add(T a, U b) -> decltype(a+b) {
    return a+b;
}

      

This is obviously also possible, but adds even more strength to the arguments for using it auto

instead.

+4


source


There are two different uses in your code auto

, one in parameters and another in return type. In the case of parameters, each use auto

introduces a unique template type argument, as Jerry mentions that it will be equivalent to:

// 1
template <typename A, typename B>
auto add(A a, B b) {
    return a + b;
}

      

In this case, if you have any constraint on different type arguments (must be the same, but may be different), then explicit template syntax provides a better alternative. This is especially important if you want to use SFINAE for arguments (using an optional template argument):

// 2
template <typename A, typename B, 
          typename _1 = typename A::iterator,  // A has nested iterator type
          typename _2 = typename B::iterator>  // So does B
auto f(A a, B b);

      

Note that I deliberately avoided using SFINAE on the return type as it interferes with your other use in some way auto

, but this could be a different option:

// 3
auto f(auto a, auto b) 
  ->     typename enable_if<has_nested_iterator<decltype(a)>::value
                         && has_nested_iterator<decltype(b)>::value, 
                           [return type] >::type;

      



But as you can see, it gets a little more complicated as you need to use the return type and get the type through the value with decltype

.

The second use auto

in your example, which is very different from this, is in the return type to have an inferred return type. The inferred return type is a function that was already available in C ++ 11 for lambdas, but has been generalized to all function templates. This function allows the compiler to find the return type of the function by examining various operators return

within the body. The advantage is that if your template has one return expression, you don't have to type that expression twice, but for the return type, it's different for the actual code:

// 4
auto add(auto a, auto b) -> decltype(a + b) {  // 'a + b' here
   return a + b;                               // 'a + b' also here
}

      

The disadvantage is that the compiler needs to check the body of the function to determine the type that will be returned, and this is a substitute for the message type if necessary. Thus, a return statement from a function that has an inferred type cannot be used in an SFINAE expression, potentially making life difficult for users of your function:

// 5
auto doAdd(auto a, auto b) 
  -> typename enable_if<is_integral<decltype(add(a,b))>>::type
{
   return add(a,b);
}

      

SFINAE will not remove the above overload doAdd

from the overload resolution set causing the hard error if you call it doAdd(1, 1.)

.

+3


source


On thinking, the only reason I see using a template function is to force multiple parameters for the same type. While with automatic inference, each type is independent from each other.

You can even mix the pattern and automatically if you want the first and third parameters to have the same type and common type for the second.

template <class T> void barb(T a,auto b,T c){}
int main(int argc, char *argv[])
{
    barb(5," ",5); //ok 
    barb(5," ",5.6); //fails
    return 0;
} 

      

As Oktalist wrote in a comment, you can use the using statement to get the type of the auto parameter.

0


source







All Articles