2

I am trying to create a forwarding wrapper function which times the call of a function in . There are 2 types I need to deal with,

  • one is timing a function which doesn't return a value, and

  • other not returning

There might be operations which I need to do before and after the call which I am skipping here, so I cannot just do return func(), which deals with both void and non-void types.

Here is the wrapper for the void function.

template<typename T, typename ...U>
auto time_function(T&& func, U&& ...args) -> typename enable_if<is_same<decltype(func(args...)), void>::value>::type
{
    std::cout << "timing void function" << std::endl;
    std::forward<T>(func)(std::forward<U>(args)...);
    std::cout << "timing over" << std::endl;
}

Wrapper for the non-void function

template<typename T, typename ...U>
auto time_function(T&& func, U&& ...args) -> typename enable_if < !is_same<decltype(func(args...)), void), decltype(func(args...)) > ::value > ::type
{
    std::cout << "timing returning function" << std::endl;
    auto val = std::forward<T>(func)(std::forward<U>(args)...);
    std::cout << "timing over" << std::endl;
    return val;
}

int main()
{
    time_function(foo, 2);
    int i = time_function(&foo_return, 1); //this generates an error
    //std::cout<<i<<std::endl;
}

foo is a function which returns void, and foo_return returns an integer. The error that is generated is

<source > :28 : 129 : error : template argument 1 is invalid
auto time_function(T && func, U && ...args) -> typename enable_if<!is_same<decltype(func(args...)), void>, decltype(func(args...))>::value > ::type
^
<source>:28 : 55 : error : expected nested - name - specifier before 'enable_if'
auto time_function(T && func, U && ...args) -> typename enable_if<!is_same<decltype(func(args...)), void>, decltype(func(args...))>::value > ::type
^ ~~~~~~~~
<source>:28 : 129 : error : template argument 1 is invalid
auto time_function(T && func, U && ...args) -> typename enable_if<!is_same<decltype(func(args...)), void>, decltype(func(args...))>::value > ::type
^
<source>:28 : 129 : error : template argument 1 is invalid
<source> : 28 : 129 : error : template argument 1 is invalid
<source> : 28 : 55 : error : expected initializer before 'enable_if'
auto time_function(T && func, U && ...args) -> typename enable_if<!is_same<decltype(func(args...)), void>, decltype(func(args...))>::value > ::type
^ ~~~~~~~~

<source>: In function 'int main()' :
    <source> : 42 : 41 : error : no matching function for call to 'time_function(int (*)(int), int)'
    int i = time_function(&foo_return, 1); //error -
^
<source>:20 : 6 : note : candidate : template<class T, class ... U> typename std::enable_if<std::is_same<decltype (func(time_function::args ...)), void>::value>::type time_function(T&&, U && ...)
auto time_function(T && func, U && ...args) -> typename enable_if<is_same<decltype(func(args...)), void>::value>::type
^ ~~~~~~~~~~~~
<source> : 20 : 6 : note : template argument deduction / substitution failed :
<source> : In substitution of 'template<class T, class ... U> typename std::enable_if<std::is_same<decltype (func(time_function::args ...)), void>::value>::type time_function(T&&, U&& ...) [with T = int (*)(int); U = {int}]' :
    <source> : 42 : 41 : required from here
    <source> : 20 : 6 : error : no type named 'type' in 'struct std::enable_if<false, void>'

To my knowledge, the wrapper is correct, what is wrong? I am checking if the return type of the function is void using is_same, and if so, declaring the return type I wish using enable_if.

JeJo
  • 30,635
  • 6
  • 49
  • 88
Sridhar Thiagarajan
  • 580
  • 1
  • 7
  • 20
  • I would avoid having that difference between the `f(args...)` used in `decltype` and the `std::forward(func)(std::forward(args)...)` actually called in the function. You might have one valid and one invalid, or they might be two different functions where one returns `void` and the other doesn't. – aschepler Nov 03 '19 at 02:50
  • @aschepler when I do the forward, it just performs a conditional static cast of the function correct? How could it be 2 different functions or invalid? – Sridhar Thiagarajan Nov 03 '19 at 02:59
  • For example, given `void f1(std::string&&);` and an attempt to do `time_function(f1, std::string{});` then `f1(arg)` is invalid but `f1(std::forward(arg))` is valid. Given `void f2(std::string&);` then `f2(arg)` is valid but `f2(std::forward(arg))` is invalid. And if `f3` has a class type with both `void operator()(std::string&);` and `int operator()(std::string&&);` then the `enable_if` sees return type `void` but the body calls the function with return type `int`. – aschepler Nov 03 '19 at 12:41
  • I understand the forwarding of the args to the function, but the function itself? – Sridhar Thiagarajan Nov 03 '19 at 19:05

2 Answers2

2

@rmawatson has already pointed out the syntax error. However, I would like to mention a couple of improvements.

Since you are using , you could achieve the same, using a less verbose version of std::enable_if, by using the helper type std::enable_if_t

Secondly, std::is_void is the better(or the suitable) trait from the standard for checking whether a type is void, which will also save some typing. Using variable templates, (also since ), that would be much shorter! (See here a live demo)

#include <iostream>
#include <type_traits> // std::enable_if_t, std::is_void
// variable templates
template<class T> constexpr bool is_void_v = std::is_void<T>::value;

template<typename T, typename ...U>
auto time_function(T&& func, U&& ...args) -> std::enable_if_t<::is_void_v<decltype(func(args...))>>
{
    // ... code here
}

template<typename T, typename ...U>
auto time_function(T&& func, U&& ...args)
-> std::enable_if_t<!::is_void_v<decltype(func(args...))>, decltype(func(args...))>
{
    // ... code here
}

However, if you have access to , you could simply write both logic in one function, using if constexpr. Just for future @todo list. (See live here)

#include <type_traits> // std::enable_if_t, std::is_void_v

template<typename T, typename ...U>
auto time_function(T&& func, U&& ...args)
{
    if constexpr (std::is_void_v<decltype(func(args...))>)
    {
        // ... timing void function"
    } 
    else
    {
        // ... timing returning function
    }
}
JeJo
  • 30,635
  • 6
  • 49
  • 88
  • Nice! Why does my line auto time_function(T&& func, U&& ...args) -> typename enable_if_t::value> give an error - expected nested name specifier? – Sridhar Thiagarajan Nov 03 '19 at 19:10
  • 2
    @SridharThiagarajan You are using helper type `enable_if_t` meaning, you do not need to use `typename` and `::type` extra! Change either to `-> typename enable_if::value>` or like I mentioned in the answer `-> enable_if_t>`. See similar problem: https://stackoverflow.com/questions/6489351/nested-name-specifier – JeJo Nov 03 '19 at 19:51
1

You have a syntax error,

-> typename enable_if<!is_same<decltype(func(args...)), void),decltype(func(args...))>::value>::type

-> typename enable_if<!is_same<decltype(func(args...)), void>::value, decltype(func(args...))>::type

#include <type_traits>
#include <iostream>
using namespace std;

template<typename T, typename ...U>
 auto time_function(T&& func, U&& ...args) 
    -> typename enable_if<is_same<decltype(func(args...)), void>::value>::type
{
    std::cout<<"timing void function"<<std::endl;
    std::forward<T>(func)(std::forward<U>(args)...);

    std::cout<<"timing over"<<std::endl;
}

template<typename T, typename ...U>
auto time_function(T&& func, U&& ...args)
 -> typename enable_if<!is_same<decltype(func(args...)), void>::value, decltype(func(args...))>::type
{
    std::cout<<"timing returning function"<<std::endl;
    auto val = std::forward<T>(func)(std::forward<U>(args)...);

    std::cout<<"timing over"<<std::endl;

    return val;
}


void foo(int){}
int foo_return(int){return 0;}

int main()
{

    time_function(foo, 2);
    int i = time_function(&foo_return, 1); //this generates an error
    std::cout<<i<<std::endl;

}

Demo

rmawatson
  • 1,909
  • 12
  • 20