3

Both of the following code compiles and performs as expected, are they different?

template<typename T, typename ...U>
auto time_function(T&& func, U&& ...args)
{
   std::cout << "timing" << std::endl;
   auto val = std::forward<T>(func)(std::forward<U...>(args...));
   std::cout << "timing over" << std::endl;
   return val;
}

template<typename T, typename ...U>
auto time_function(T&& func, U&& ...args)
{
   std::cout << "timing" << std::endl;
   auto val = std::forward<T>(func)(std::forward<U>(args)...);
   std::cout << "timing over" << std::endl;
   return val;
}

Looking at SO How would one call std::forward on all arguments in a variadic function?, second seems to be recommended, but doesn't the first do the same thing?

JeJo
  • 30,635
  • 6
  • 49
  • 88
Sridhar Thiagarajan
  • 580
  • 1
  • 7
  • 20

1 Answers1

3

They are not the same. They are the same in the case that the arity of args is 1 or 0. otherwise it will fail to compile, consider..

#include <iostream>
using namespace std;
template<typename T, typename ...U>
auto time_function_1(T&& func, U&& ...args)
{

    std::cout<<"timing"<<std::endl;
    auto val = std::forward<T>(func)(std::forward<U...>(args...));
    std::cout<<"timing over"<<std::endl;
    return val;
}

    template<typename T, typename ...U>
auto time_function_2(T&& func, U&& ...args)
{

    std::cout<<"timing"<<std::endl;
    auto val = std::forward<T>(func)(std::forward<U>(args)...);
    std::cout<<"timing over"<<std::endl;
    return val;
}



int f (int){return 0;}

int y (int,int){return 0;}

int main() {
    time_function_1(f,1);
    time_function_2(f,1);

    time_function_1(y,1,2); // fail
    time_function_2(y,1,2);
    return 0;
}

Demo

for the failing case std::forward<U...>(args...) expands to forward<int, int>(int&, int&) and will fail to compile.

std::forward<U>(args)... expands to std::forward<int>(int&),std::forward<int>(int&)

rmawatson
  • 1,909
  • 12
  • 20
  • so forward can take only a single template argument? – Sridhar Thiagarajan Nov 01 '19 at 00:35
  • yes, correct, see here -> https://en.cppreference.com/w/cpp/utility/forward – rmawatson Nov 01 '19 at 00:36
  • since you are passing a temporary, shouldn't it expand to forward(int&&, int&&), are am I misunderstanding – Sridhar Thiagarajan Nov 01 '19 at 00:38
  • Its a named temporary now. inside of time_function the ints are all part of the named parameter pack called args. This is the whole point in std::forward. it will transform it back in this case to int&& – rmawatson Nov 01 '19 at 00:42
  • Ah yes, as for the template parameter type, shouldn't it be int&&? To my understanding, std::forward knows whether to cast or not based on the template inference type – Sridhar Thiagarajan Nov 01 '19 at 00:43
  • 1
    This might clear up whats happening.The important thing in the example below is that U is not just 'int' in each case, if its an lvalue it will be int& and reference collapsing rules apply https://ideone.com/cfK0cb. This should give you a feel for how 'std::forward knows whether to cast or not' – rmawatson Nov 01 '19 at 01:02