cmannett85
cmannett85

Reputation: 22366

C++ Trying to create an 'Intermediate' functor

What I mean by an 'intermediate' functor is: a normal functor where one of the arguments can be specified at call-time. The problem is that I have an animation timeline (essentially scalar values at particular frames), and the output of this needs to be piped to and from getter/setter methods within the objects to be animated. Here is a simplified example of what I was attempting:

template < class ObjType, class Getter, class Setter, typename Scalar >
class Sy_propertyBridge : public Sy_abstractPropertyBridge
{
public:
                        Sy_propertyBridge( ObjType* object, Getter getter,
                                           Setter setter )
                            : obj_( object ), get_( getter ),
                              set_( setter ) {}
    virtual            ~Sy_propertyBridge() {}

    inline virtual float get() const
                        {
                            //  Cannot handle default arguments.
                            Scalar tmp = ( obj_->*get_ )();
                            return static_cast< float >( tmp );
                        }
    inline virtual void set( float value )
                        {
                            Scalar tmp = static_cast< Scalar >( value );
                            ( obj_->*set_ )( tmp );
                        }
private:
    ObjType*            obj_;
    Getter              get_;
    Setter              set_;
};

The timeline only holds floats, so whatever scalar type the object uses for it's getter/setter methods, must be cast (I have a partial specialisation for floats that does away with the cast). ObjType is the animated object type, Getter and Setter are pointers-to-methods, and Scalar is the type Getter and Setter and expected to deal with.

I thought this would be fine, but compilation failed because some of the getter/setters had other default-initialised arguments. I didn't think this would be a problem, because they're default! But when the compiler expected more args for the pointer-to-method than I was supplying, it failed.

I then tried using variadic template args so the default values could be manually put in, but fell at the first hurdle because I can't store the parameter pack as a member to be repeatedly applied as args to the pointer-to-methods. I have also been looking at std::function and std::bind, I was hoping to be able to store a std::function as a member with the getter/setter method's default args preset - and change the appropriate arg for one from the timeline just before calling. Only I can't find a way to do that...

Does anyone have any suggestions to achieve what I am aiming for? Or is my design fundamentally flawed and there is an easier approach?

Upvotes: 1

Views: 302

Answers (2)

jpalecek
jpalecek

Reputation: 47770

std::function would be the way to go. Will just use std::function<Scalar(const ObjType*)> as your getter, std::function<void(ObjType*, Scalar)> as your setter (if the Scalars are implicitly convertible to/from floats, I'd even use std::function<float(ObjType const*)> and std::function<void(ObjType*, float)>, resp.). You can initialize these eg. with lambda functions:

Sy_propertyBridge(my_obj, 
  [](const MyObjType* o) -> float { return o->opacity; }, 
  [](const MyObjType* o, float f) { o->opacity=f; })

Note that there may be more elegant ways of doing this (eg. there could be only one function that could serve both as a getter and a setter).

Taking it even further, you could

  • get rid of the obj_ member variable
  • get rid of the o parameters to the functions

The lambdas would then have to remember the object they will operate on. So the above constructor call would become

Sy_propertyBridge(
  []() -> float { return my_obj->opacity; }, 
  [](float f) { my_obj->opacity=f; })

Upvotes: 1

Kevin Anderson
Kevin Anderson

Reputation: 7010

From what I understand, you are trying to use "get" and "set" methods that vary per class you're passing in here? If so, I think you should be trying to use a purely virtual base class on your "ObjType" objects, and then implement that in your classes you're passing in. In C#/Java terms, an interface.

Basically this:

class IGetSetter
{
    virtual float get() const = 0;
    virtual void set(float) = 0;    
}

class ObjType1 : public IGetSetter
{
    virtual float get() const
    {
        // Implementation
    }

    virtual void set(float a)
    {
        // Implementation
    }
}

class ObjType2 : public IGetSetter
{
    virtual float get() const
    {
        // Implementation
    }

    virtual void set(float a)
    {
        // Implementation
    }
}

Then your class becomes:

template < typename Scalar >
class Sy_propertyBridge : public Sy_abstractPropertyBridge
{
public:
    Sy_propertyBridge( IGetSetter* object)
        : obj_( object ) {}
    virtual ~Sy_propertyBridge() {}

    inline virtual float get() const
    {
        Scalar tmp = obj_->get();  // Uses polymorphism to find the right method
        return static_cast< float >( tmp );
    }
                        }
    inline virtual void set( float value )
    {
        Scalar tmp = static_cast< Scalar >( value );
        obj_->set( tmp );  // Uses polymorphism to find the right method
    }
private:
    ObjType* obj_;
};

But actually, there's probably a straightforward way of axing your Sy_propertyBridge class entirely. Just store an array of pointers to IGetSetter and then invoke those directly if they do what you want.

Upvotes: 0

Related Questions