【问题标题】:How to write a member function with variadic parameters as template parameter如何使用可变参数作为模板参数编写成员函数
【发布时间】:2020-07-08 21:46:50
【问题描述】:

是否可以像下面这样在 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)...);
}

所以,对于一个测试类

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

【问题讨论】:

  • Test 成员定义中的语法错误:方法定义后有多余的分号,return 语句和结构的右大括号后缺少分号。
  • 注意 C++17 中有std::invoke
  • @Jarod42 虽然不能直接将指向重载成员函数的指针传递给std::invoke
  • @aschepler:std::invoke(static_cast&lt;bool (Test::*)(bool)&gt;(&amp;Test::Func), test, true);std::invoke([](Test&amp; test, auto... args){ return test.Func(args...);}, test, true);
  • @Jarod 这就是为什么我一定要“直接”输入;)

标签: c++ c++11 templates c++14 variadic-templates


【解决方案1】:

我想通了。它就像 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;
}

【讨论】:

    【解决方案2】:

    问题

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

      Demo

    • 或使参数不可推导(我使用 C++20 中的std::type_identity,但可以在以前的版本中轻松地重新实现):

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

      Demo

    【讨论】:

    • 第一种解决方案不适用于过载情况。 godbolt.org/z/joUTQw 第三种解决方案不适用于所有情况。 godbolt.org/z/D8g-Kq我叫错了?
    • @foo:您选择重载的方式是错误的,修复了最后一个解决方案中的错字。添加了演示
    【解决方案3】:

    在你的Call声明中:

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

    函数模板采用(或可能尝试推断)两个或多个模板参数:第一个是T,第二个是R,其余的是Args。因此,在Call&lt;int()&gt;Call&lt;bool(bool)&gt; 中将单个函数类型作为第一个模板参数是错误的。正确的称呼方式是

    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 和条件异常说明符以使其更通用。)

    这也解决了原始问题,它不能用于调用 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 就可以了,因为任何非静态成员函数都可以匹配FuncT T::*。如果我们想为 driver_Test2 中的调用提供一个模板参数,可能是因为成员函数被重载,那看起来像 Call&lt;int() const&gt;Call&lt;void() &amp;&gt;

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-11
      • 2019-12-20
      • 2016-06-25
      • 2012-03-13
      • 1970-01-01
      • 2015-05-06
      相关资源
      最近更新 更多