【问题标题】:Callback with variadic template to ambiguous overloads使用可变参数模板回调模棱两可的重载
【发布时间】:2011-04-09 23:22:36
【问题描述】:

在 C++0X 中,我想使用可变参数模板编写通用调用者/回调函数。第一个障碍:被调用者是成员函数。到目前为止,一切都很好。第二个障碍:有很多重载的同名成员函数。

我将如何解决这个问题?我的主要参考是这个fine article,但我不能让它工作。

好的,让我们开始吧:

Class Foo
{
    void bar(int ID, int, int) { ... }
    void bar(int ID) { ... }
    void bar(int ID, double, float, void(Baz::*)()) const { /* jikes */ }

    template<typename ... Args>
    void sendBarToID_15(std::function<void(int, Args...)> refB, Args ... args)
    {
        refB(15, args...);
    }

    void yum()
    {
        sendBarToID_15(&Foo::bar, this, 17, 29); // want first version
    }
};

但是,我无法在 yum() 中编译调用,因为重载会阻止模板解析。根据引用的文章,我应该显式创建一个函数对象

f = magic::make_function<help, me>(&Foo::bar)

然后悠闲地拨打sendBarToID_15(f, this, 17, 29)

  1. 我怎样才能做到这一点?

  2. std::bind 魔法在最后一行中消除了“this”的加分。

  3. 以有用的方式制作 15 个参数的额外奖励积分。

非常感谢!!

【问题讨论】:

    标签: templates c++11 bind variadic


    【解决方案1】:

    这就是你要找的吗?

    #include <functional>
    #include <iostream>
    
    class Baz;
    
    class Foo
    {
        void bar(int ID, int, int) { std::cout << "here\n"; }
        void bar(int ID) { /*...*/ }
        void bar(int ID, double, float, void(Baz::*)()) const { /* jikes */ }
    
        template<int ID, typename ... Args>
        void sendBarToID(std::function<void(int, Args...)> refB, Args&& ... args)
        {
            refB(ID, std::forward<Args>(args)...);
        }
    
    public:
        void yum()
        {
            using namespace std::placeholders;
            void (Foo::*mfp)(int, int, int) = &Foo::bar;
            sendBarToID<15>(std::function<void(int, int, int)>
                (std::bind(mfp, this, _1, _2, _3)), 17, 29); // want first version
        }
    };
    
    int main()
    {
        Foo foo;
        foo.yum();
    }
    

    【讨论】:

    • 看起来不错,将在一分钟内测试...有没有办法避免显式函数指针 mfp 并为此使用一些神奇的 STL 容器,std::mem_fun 或类似的东西?
    • 我想不知何故,在某个地方,你需要用签名指定你想要的重载。无论是在 yum 中明确还是在其他地方(可能是隐含地)都可能是可以协商的。不幸的是,我目前对您的设计限制还不够了解,无法提出任何建议。
    • 我的意思是避免写指针的东西,比如std::mem_fun&lt;void(int, int, int)&gt; f = &amp;Foo::bar——本质上是一样的,只是以某种方式结束了......但这看起来已经很好了。
    • 啊,所以发生了一些非常奇怪的事情。在我的真实案例中,类型不都是ints,而是我有类似void bar(int ID, const Zip &amp;, char)的东西。然后我有一个对象Zip z;,但我不能只调用sendBarToID&lt;15&gt;(....., z, 'a'),而是必须显式转换z,即我调用sendBarToID&lt;15&gt;(....., (const Zip &amp;)(z), 'a')。多么奇怪。
    • 不管怎样,我必须将函数签名拼写两次,这仍然有点烦人。你会认为一次就足以指定我想要的重载......对std::bind的调用肯定能够推断出mfp的模板参数。
    【解决方案2】:

    使用 lambda 函数——不再需要这种东西了。

    class Foo
    {
        void bar(int ID, int, int) { ... }
        void bar(int ID) { ... }
        void bar(int ID, double, float, void(Baz::*)()) const { /* jikes */ }
    
        template<typename ... Args>
        void sendBarToID_15(std::function<void(int, Args...)> refB, Args ... args)
        {
            refB(15, args...);
        }
    
        void yum()
        {
            sendBarToID_15([&, this](int i) {
                this->bar(i, 17, 29);
            });
        }
    };
    

    【讨论】:

      【解决方案3】:

      您正尝试使用以下参数按顺序调用 bar:15、this、17、29。

      你想要:这个、15、17、29

      template<typename ... Args>
      void sendBarToID_15(std::function<void(int, Args...)> refB, Args ... args)
      {
          refB(15, args...);
      }
      

      所以 &Foo::bar 不能是 std::function

      如果你可以使用 lambda,我会使用:

       void yum()
       {
          // EDITED: was "Foo* t"
          // (I don't remember of capture works with this, you may not need this)
          Foo* p = this;
          sendBarToID_15([p](int x, int y, int z){ p->bar(x, y, z); }, 17, 29);
       }
      

      如果你不能用辅助类来实现它:

      class helper {
      private:
         Foo* p;
      public:
         [...]
         void operator(int x, int y, int z) {
            p->bar(x,y,z);
         }
      }
      

      或使用绑定:

      // EDITED: wrong return type was used
      void (Fred::*f)(char x, float y) = &Foo::bar;
      sendBarToID_15(std::bind(f, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), 17, 29);
      

      【讨论】:

      • Hmmmm....1) 为什么 &Foo::bar 不能是 std::function? 2) Foo* t 是干什么用的? 3)助手类是不可行的,我会有很多像“foo”这样的东西,不能为每个人写一个助手。 4) &Foo::bar 如何转换为 int(Fred::*)(char, float)?否则,这与之前的答案相似,我将对其进行测试!
      • "&Foo::bar 如何转换为 int(Fred::*)(char, float)?" : 确实是签名有问题
      【解决方案4】:

      为了跟进霍华德的精彩回答,让我声明一下,最后我得出的结论是,将 sendBarToID 函数模板化并没有真正按照我希望的方式改进设置的逻辑。由于无论如何我们都必须绑定(),所以没有理由先绑定然后取消绑定占位符,我们不妨将所有内容绑定到位。这是非模板版本:

      void sendBarToID_15(std::function<void(int)> f)
      {
          f(15);
      }
      
      void yum()
      {
          // No avoiding this in the presence of overloads
          void (Foo::*mfp)(int, int, int) = &Foo::bar;
      
          sendBarToID_15(std::bind(mfp, this, std::placeholder::_1, 17, 29));
      }
      

      我希望可变参数模板解决方案能以某种方式使客户端代码更简单,但现在我不明白它怎么能比这更简单。可变参数 #define 宏负责其余部分。

      感谢您的贡献!

      更新:好的,这就是我最终想到的,感谢预处理器宏:

      #include <functional>
      #include <iostream>
      
      class Baz;
      
      class Foo
      {
          void bar(int ID, const int &, int)
          { std::cout << "v1 called with ID " << ID << "\n"; }
          void bar(int ID)
          { std::cout << "v2 called with ID " << ID << "\n"; }
          void bar(int ID, double, float, void(Baz::*)()) const
          { std::cout << "v3 called with ID " << ID << "\n"; }
      
          void innocent(int ID, double)
          { std::cout << "innocent called with ID " << ID << "\n"; }
      
          void very_innocent(int ID, double) const
          { std::cout << "very innocent called with ID " << ID << "\n"; }
      
          template<int ID> void sendBarToID(std::function<void(int)> refB) { refB(ID); }
          template<int ID> void sendConstBarToID(std::function<void(int)> refB) const { refB(ID); }
      
      #define MAKE_CALLBACK(f, ...) std::bind(&Foo::f, this, std::placeholders::_1, __VA_ARGS__)
      #define MAKE_EXPLICIT_CALLBACK(g, ...) std::bind(g, this, std::placeholders::_1, __VA_ARGS__)
      
      #define MAKE_SIGNED_CALLBACK(h, SIGNATURE, ...) MAKE_EXPLICIT_CALLBACK(static_cast<void (Foo::*)SIGNATURE>(&Foo::h), __VA_ARGS__)
      #define MAKE_CONST_SIGNED_CALLBACK(h, SIGNATURE, ...) MAKE_EXPLICIT_CALLBACK(static_cast<void (Foo::*)SIGNATURE const>(&Foo::h), __VA_ARGS__)
      
      public:
          void gobble()
          {
            double q = .5;
            int    n = 2875;
            void(Baz::*why)();
      
            sendBarToID<5>(MAKE_CALLBACK(innocent, q));
            sendConstBarToID<7>(MAKE_CALLBACK(very_innocent, q));
            // sendBarToID<11>(MAKE_SIGNED_CALLBACK(bar, (int))); // can't do, too much commas
            sendBarToID<13>(MAKE_SIGNED_CALLBACK(bar, (int, const int &, int), n, 1729));
            sendConstBarToID<17>(MAKE_CONST_SIGNED_CALLBACK(bar, (int, double, float, void(Baz::*)()), q, q, why));
          }
      
          void yum() const
          {
            double q = .5;
            int    n = 2875;
            void(Baz::*why)();
      
            sendConstBarToID<2>(MAKE_CALLBACK(very_innocent, q));
            // sendBarToID<-1>(MAKE_CALLBACK(innocent, q)); // Illegal in const function
      
            sendConstBarToID<3>(MAKE_CONST_SIGNED_CALLBACK(bar, (int, double, float, void(Baz::*)()), q, q, why));
          }
       };
      
      int main()
      {
          Foo foo;
          foo.yum();
          foo.gobble();
      }
      

      有一个不便之处:我需要为常量和非常量成员函数定义两个单独的函数和宏。另外,我无法处理空参数列表 (Foo::bar(int))。

      【讨论】:

        猜你喜欢
        • 2012-12-09
        • 2011-12-19
        • 1970-01-01
        • 2015-01-12
        • 1970-01-01
        • 2021-07-26
        • 2016-12-27
        • 1970-01-01
        • 2011-06-30
        相关资源
        最近更新 更多