Как написать функцию-член с параметрами variadi c в качестве параметра шаблона - PullRequest
1 голос
/ 28 марта 2020

Можно ли написать шаблонную функцию на C ++ 14, как показано ниже

Вот пример 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)...);
}

Итак, для теста class

struct Test 
{
  int Func(){return 1;};
  bool Func(bool){return true;};  // overload

  void FuncInt(int){};
};

Шаблон может работать для приведенного ниже варианта использования (но не удалось)

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;
}

Здесь информация об ошибке.

#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

Ответы [ 3 ]

2 голосов
/ 28 марта 2020

Проблема с

template <typename T, typename R, typename... Args>
decltype(auto) Call(T& obj, R(T::*mf)(Args...), Args&&... args)

заключается в том, что Args выводится дважды и должно быть идентичным.

Существует несколько способов решения этой проблемы:

  • Добавить дополнительный параметр шаблона:

    template <typename T, typename R, typename... Args, typename ... Ts>
    decltype(auto) Call(T& obj, R(T::*mf)(Args...), Ts&&... args)
    {
        return (obj.*mf)(std::forward<Ts>(args)...);
    }
    

    Демо

  • или сделать параметр не выводимым (я использую std::type_identity из C ++ 20, но тривиально может быть переопределено в предыдущей версии):

    template <typename T, typename R, typename... Args>
    decltype(auto) Call(T& obj, R(T::*mf)(Args...), std::type_identity_t<Args>... args)
    {
        return (obj.*mf)(std::forward<Args>(args)...);
    }
    
  • или полностью изменить подпись:

    template <typename T, typename M, typename... Args>
    decltype(auto) Call(T& obj, M mf, Args&&... args)
    {
        return (obj.*mf)(std::forward<Args>(args)...);
    }
    

    Демо

1 голос
/ 28 марта 2020

В вашем объявлении 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() &>.

0 голосов
/ 28 марта 2020

Я понял это. Это тихо, как std :: men_fn.

Вот код ниже и пример https://godbolt.org/z/NoWPV_


#include <type_traits>

template<typename T>
struct Proxy
{
    template<typename R, typename ...Args>
    decltype(auto) Call(T& obj, R T::*mf, Args&&... args)
    {
        return (obj.*mf)(std::forward<Args>(args)...);
    }
};

struct Test 
{
  int Func(){return 1;};
  bool Func(bool){return true;};  // overload

  void FuncInt(int){};
};

int main()
{
  Test test;
  Proxy<Test> proxy;

  // for overload case
  auto a = proxy.Call<int()>(test, &Test::Func);
  auto b = proxy.Call<bool(bool)>(test, &Test::Func, true);

  // for non-overload case
  proxy.Call(test, &Test::FuncInt, 1);

  return 0;
}
...