Is it possible to write template function in C++14 like below
Here is the sample https://godbolt.org/z/9gRk-t
// pseudo code
#include <type_traits>
template <typename T, typename R, typename... Args>
decltype(auto) Call(T& obj, R(T::*mf)(Args...), Args&&... args)
{
return (obj.*mf)(std::forward<Args>(args)...);
}
So, for a test class
struct Test
{
int Func(){return 1;};
bool Func(bool){return true;}; // overload
void FuncInt(int){};
};
The template coudl work for the use case below (but it failed)
int main()
{
Test test;
// for overload case
auto a = Call<int()>(test, &Test::Func);
auto b = Call<bool(bool)>(test, &Test::Func, true);
// for non-overload case
Call(test, &Test::FuncInt, 1);
return 0;
}
Herer the erro info.
#1 with x64 msvc v19.24
example.cpp
<source>(23): error C2672: 'Call': no matching overloaded function found
<source>(23): error C2770: invalid explicit template argument(s) for 'decltype(auto) Call(T &,R (__cdecl T::* )(Args...),Args &&...)'
<source>(5): note: see declaration of 'Call'
<source>(24): error C2672: 'Call': no matching overloaded function found
<source>(24): error C2770: invalid explicit template argument(s) for 'decltype(auto) Call(T &,R (__cdecl T::* )(Args...),Args &&...)'
<source>(5): note: see declaration of 'Call'
Compiler returned: 2
In your declaration of Call
:
template <typename T, typename R, typename... Args>
decltype(auto) Call(T& obj, R(T::*mf)(Args...), Args&&... args);
the function template takes (or might try to deduce) two or more template arguments: the first is T
, the second is R
, and the rest are Args
. So giving a single function type as the first template argument as in Call<int()>
and Call<bool(bool)>
is wrong. The correct way to call it would be
auto a = Call<Test, int>(test, &Test::Func);
auto b = Call<Test, bool, bool>(test, &Test::Func, true);
Another issue is that if you want the template arguments deduced, as in the non-overloaded case, since the Args
pack appears twice, it will only work if the lists deduced from the member function and from the trailing arguments are exactly the same:
int n = 3;
Call(test, &Test::FuncInt, n); // error!
// Args... is deduced to `int` from `&Test::FuncInt`, but deduced to `int&`
// from `n` since it's an lvalue matching a forwarding reference parameter.
If you prefer the function type syntax, you could use the solution of @foo:
template <typename FuncT, typename T, typename... Args>
constexpr decltype(auto) Call(T& obj, FuncT T::*mf, Args&&... args)
noexcept(noexcept((obj.*mf)(std::forward<Args>(args)...)))
{
return (obj.*mf)(std::forward<Args>(args)...);
}
// main() exactly as in question, including Call<int()> and Call<bool(bool)>.
FuncT T::*mf
is the syntax for declaring a pointer-to-member, which is often used to point to a data member, but also works to point to a function if the type FuncT
is a function type. (I've added the constexpr
and conditional exception specifier to make it more generic.)
This also solves an issue with the original, which can't be used to invoke a member function which is const
or which has a ref-qualifier, since this creates a different function type:
class Test2 {
public:
int get() const;
void set() &;
};
void driver_Test2() {
Test2 test;
// Error with original Call:
// Type of &Test2::get is "int (Test2::*)() const",
// which cannot match "R (Test2::*)(Args...)"
int a = Call(test, &Test2::get);
// Error with original Call:
// Type of &Test2::set is "void (Test2::*)(int) &",
// which cannot match "R (Test2::*)(Args...)"
Call(test, &Test2::set, 1);
}
But with the new Call
definition, driver_Test2
is fine, since any non-static member function can match FuncT T::*
. If we wanted to supply a template argument to the calls in driver_Test2
, maybe because the member functions are overloaded, that would look like Call<int() const>
and Call<void() &>
.