В вашем объявлении Call
:
template <typename T, typename R, typename... Args>
decltype(auto) Call(T& obj, R(T::*mf)(Args...), Args&&... args);
шаблон функции принимает (или может попытаться вывести) два или более аргументов шаблона: первый - T
, второй - R
а остальные Args
. Поэтому указывать один тип функции в качестве первого аргумента шаблона, как в Call<int()>
и Call<bool(bool)>
, неверно. Правильный способ его вызова будет
auto a = Call<Test, int>(test, &Test::Func);
auto b = Call<Test, bool, bool>(test, &Test::Func, true);
Другая проблема заключается в том, что если вы хотите, чтобы аргументы шаблона выводились, как в случае без перегрузки, поскольку пакет Args
появляется дважды, он будет работать только если списки, выведенные из функции-члена и из конечных аргументов, в точности совпадают:
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.
Если вы предпочитаете синтаксис типа функции, вы можете использовать решение @ 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
- это синтаксис объявления указателя на член, который часто используется для указания на элемент данных, но также работает для указания на функцию, если тип FuncT
равен тип функции. (Я добавил constexpr
и спецификатор условных исключений, чтобы сделать его более обобщенным c.)
Это также решает проблему с оригиналом, которую нельзя использовать для вызова функции-члена, которая равен const
или имеет квалификатор ref, так как это создает другой тип функции:
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);
}
Но с новым определением Call
, driver_Test2
хорошо, так как любой не-статус c функция-член может соответствовать FuncT T::*
. Если бы мы хотели предоставить аргумент шаблона для вызовов в driver_Test2
, возможно, из-за перегрузки функций-членов, это выглядело бы как Call<int() const>
и Call<void() &>
.