1

In my C++ project I decided to give the new++ features a try. One of those features is the binding of a function with std::function via std::bind.

Now I came to a use case, where I have to rebind the std::function.

Consider the following simplified code:

class Test
{
public:
    void first()
    {
        second(bind(&Test::third, this));
    }

    void second(function<void()> fun)
    {
        Test *other = new Test();
        fun->rebindLastParameter(other); // How can I do this?
        fun();
    }

    void third()
    {
        // This is called in context of "other"
    }
}

How can I do the fun->rebindLastParameter(other); part (replace this pointer with other)?

(EDIT) Context:

In my application there are several classes which inherit from a class called BaseModel. Those classes are automatically transpiled from a self made description language. The following class represents a simple "asteroid" which consists of two other "asteroids":

#pragma once

#include "BaseModel.h"
#include <functional>

using namespace std;
using namespace std::placeholders;

class Test : public BaseModel
{
public:
  Test(const OBB& start_obb) : BaseModel(start_obb) {}

  void __init() {
    scene();
  }
  void scene() {
      combine(bind(&Test::asteroid, this),bind(&Test::asteroid, this));
  }

  void asteroid() {
      translate(random_float(),random_float(),random_float());
      sphere(7);
      repeat(400,bind(&Test::impact, this));
  }

  void impact() {
      auto p1 = random_surface_point();
      select_sphere(p1,random_float() * 2 + 1,1.0);
      normalize(p1);
      translate_selection(-p1 * random_float() * 0.4);
  }

};

The problem lies in the function BaseModel::combine, that combines (via constructive solid geometry) two new objects (1st asteroid and 2nd asteroid):

void BaseModel::combine(function<void()> left, function<void()> right)
{
    BaseModel *leftModel = (BaseModel*) ::operator new (sizeof(BaseModel));
    BaseModel *rightModel = (BaseModel*) ::operator new (sizeof(BaseModel));
    leftModel->initWithStartOBB(*this->obb);
    rightModel->initWithStartOBB(*this->obb);

    auto newLeft = bind(left, leftModel); // This does not work
    auto newRight = bind(right, rightModel);  // This does not work

    newLeft();
    newRight();

   // ... CSG stuff
}

As leftModel and rightModel have to be new models of the same class, I need to rebind the first parameter I previously give in my automatically transpiled class Test::scene.

Perhaps I'm on the wrong track. I hope that additional context could explain why I've run into that problem.

Patryk
  • 22,602
  • 44
  • 128
  • 244
Timm
  • 1,005
  • 8
  • 20
  • 1
    `third()` does not take any parameters, so why do you want to use `bind` here? – 463035818_is_not_an_ai Feb 19 '16 at 13:09
  • 1
    @tobi303 `third()` is a member function and as such the first parameter is always the pointer of the `Test` object it is executed on. – Simon Kraemer Feb 19 '16 at 13:10
  • @Timm: please provide more info as it is not clear what do you want to achieve – Michał Walenciak Feb 19 '16 at 13:11
  • @SimonKraemer yes I know, but then I would probably simply call the methods with differrent objects rather than `bind` the methods to different objects – 463035818_is_not_an_ai Feb 19 '16 at 13:13
  • @tobi303 Yeah, I don't think the example shows the real problem... – Simon Kraemer Feb 19 '16 at 13:15
  • @MichałWalenciak I want to replace the already bound parameter of the function object. I will edit my question to give more context. – Timm Feb 19 '16 at 13:16
  • 1
    even ignoring the fact that `third` is a class method, I dont understand why "rebinding" would be necessary. Just create another `function` by binding `third` to some other parameter, you do not have to reuse the old function. – 463035818_is_not_an_ai Feb 19 '16 at 13:18
  • I hope `BaseModel` has a trivial default constructor and has no virtual functions, otherwise `leftModel->initWithStartOBB(*this->obb);` on uninitialized memory returned by `operator new` is undefined behaviour. You can't call a member function on an object that doesn't exist yet. – Jonathan Wakely Feb 19 '16 at 13:37

3 Answers3

3

As @tobi303 and others have noted, your function signature for the argument of second is too constrained, as it takes a nullary function. There is nothing to rebind - it doesn't take any parameters.

In order to achieve something like it looks like you're trying to do, you need to have the argument of second be less constricted. There are several ways of doing so (making it take a function, or a pointer to a member function); the following shows it template-parameterized by a function that takes a unary argument:

#include <functional>                                                                                                                           


using namespace std;


class Test
{   
public:
    void first()
    {   
        second([](Test *p){p->third();});
    }   

    template<class Fn> 
    void second(Fn fn) 
    {   
        Test *const other = new Test();
        fn(other);
    }   

    void third()
    {   
        // This is called in context of "other"
    }   
};  


int main()
{   
    Test t;
    t.first();
}   

Personal viewpoint (one that earned me several downvotes): I think the use of many libraries and techniques, most cases of bind for sure, most cases of function - simply precede current lambda functions, and there is less and less of a need for them.

Ami Tavory
  • 74,578
  • 11
  • 141
  • 185
2

How can I do the fun->rebindLastParameter(other); part (replace this pointer with other)?

You can't, in general.

std::function relies on type-erasure which means the original type of the object that was stored in the std::function is not part of the type, and not easily accessible.

If you know for certain that the fun definitely stores an object returned by bind(&Test::third, this) then there's no need to rebind the argument, just modify fun to hold a completely different function object with the right argument, i.e.

fun = bind(&Test::third, other);

If you know it definitely stores an object returned by bind( &Test::???, other) where &Test::??? is any member function with exactly the same signature as Test::third then you could do something like this:

using binder_type = decltype(bind(&Test::third, this));
if (auto target = fun.target<binder_type>())
{
   // *target is the result of the bind() expression
}

But there's still no way to modify the object at *target to replace the Test* pointer it holds.

If you know the set of functions that could have been used then you could do:

using binder_foo_type = decltype(bind(&Test::foo, this));
using binder_bar_type = decltype(bind(&Test::bar, this));
using binder_baz_type = decltype(bind(&Test::baz, this));
if (fun.target<binder_foo_type>())
{
   fun = std::bind(&Test::foo, other);
}
else if (fun.target<binder_bar_type>())
{
   fun = std::bind(&Test::bar, other);
}
else if (fun.target<binder_baz_type>())
{
   fun = std::bind(&Test::baz, other);
}

But these are not general purpose solutions and not very maintainable.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
1

std::mem_fn is what you are looking for, as it generates wrapper objects for pointers to members.

std::function<void(Test*)> f = std::mem_fn(&Test::third);
f(this);
Test *other = new Test();
f(other);

With std::bind, use fun with a reference to Test object. This way you can call it with a new object

void second(std::function<void(const Test&)> fun)
{
    Test *other = new Test();
    fun(*other);
}

Or with this object

fun(*this);
hlscalon
  • 7,304
  • 4
  • 33
  • 40
  • Thanks, I now use `std::mem_fn` and pass `this` per default and `other` in my special use case. – Timm Feb 19 '16 at 14:00