Reputation: 85
I am trying to use SFINAE to create function call wrappers using std::result_of to get the return type of the function. Small sample to reproduce the problem:
void test(int) {}
template<typename T, typename... Args, typename R = typename std::result_of<T(Args...)>::type, typename std::enable_if<!std::is_void<R>::value, int>::type* = nullptr>
int test2(R& ret_param, Args... args)
{
T* test_call = test;
ret_param = test_call(args...);
return 0;
}
template<typename T, typename... Args, typename std::enable_if<std::is_void<typename std::result_of<T(Args...)>::type>::value, int>::type* = nullptr>
int test2(Args... args)
{
T* test_call = test;
test_call(args...);
return 0;
}
int main()
{
test2<decltype(test)>(1);
}
compiling this with gcc 4.9.2 results in:
68:26: error: no matching function for call to 'test2(int)'
68:26: note: candidates are:
46:5: note: int test2(R&, Args ...) [with T = void(int); Args = {}; R = int; typename std::enable_if<(! std::is_void<R>::value), int>::type* <anonymous> = 0u]
46:5: note: no known conversion for argument 1 from 'int' to 'int&'
56:5: note: template<class T, class ... Args, typename std::enable_if<std::is_void<typename std::result_of<_Functor(_ArgTypes ...)>::type>::value, int>::type* <anonymous> > int test2(Args ...)
56:5: note: template argument deduction/substitution failed:
55:142: error: function returning a function
55:142: note: invalid template non-type parameter
The problem then is that somehow "typename std::result_of::type" evaluates to a function returning a function? What is the correct way to use SFINAE to force overload resolution to pick a different function if the return type of the template function is void? Worth mentioning that in the reverse case where the test function returns an int. Additionally, if the enable_if is removed for the std::is_void resolution it will work in this case (but obviously then both functions will be valid resolution if there is a return type and compilation will fail when it picks the one not expecting a return value).
Upvotes: 2
Views: 1750
Reputation: 50540
If you accept to pass the test function as a parameter, you don't need all that boilerplate:
void test(int) {}
int test(double) { return 0; }
template<typename R, typename... Args>
int test2(R(&f)(Args...), R& ret_param, Args... args)
{
ret_param = f(args...);
return 0;
}
template<typename... Args>
int test2(void(&f)(Args...), Args... args)
{
f(args...);
return 0;
}
int main()
{
test2(test, 1);
int r;
test2(test, r, .0);
}
Deduction and overloading will do the work for you.
Upvotes: 1
Reputation: 145204
Essentially you need to form T_ref(Args...)
as the argument to std::result_of
.
The following compiles. I've applied a few simplifications and reformatting.
By the way I don't think this is a good idea. Instead just have different names for the two functions. They do need different arguments, after all. Explicit is good. Implicit is bad.
#include <type_traits> // std::(enable_if, result_of)
void test(int) {}
int foo() { return 0; }
template<
class T, class R, class... Args,
class T_ref = T&,
typename std::enable_if<
!std::is_void<
std::result_of_t<T_ref(Args...)>
>::value
>::type* = nullptr
>
int test2(R& ret_param, Args... args)
{
static_assert( std::is_same<
R,
std::result_of_t<T_ref(Args...)>
>::value, "!" );
T* test_call = foo;
ret_param = test_call(args...);
return 0;
}
template<
class T, class... Args,
class T_ref = T&,
typename std::enable_if<
std::is_void<
std::result_of_t<T_ref(Args...)>
>::value
>::type* = nullptr
>
int test2(Args... args)
{
T* test_call = test;
test_call(args...);
return 0;
}
int main()
{
test2<decltype(test)>( 1 );
int result;
test2<decltype(foo)>( result );
}
Upvotes: 2