unprotested
unprotested

Reputation: 31

How to declare an array of pointers to member functions in C++

I am doing a small project using arduino and I need to use an array of methods.

I've tried doing what I would normally do in C# / Java but that didn't work. I went online and tried many different examples and I kept getting lead to the same error message.

class ColourSensor{

public:
  void (*routine[3])() = {
        routine0,
        routine1,
        routine2
    };

  void routine0();
  void routine1();
  void routine2();
};

When I compile I get the following error:

cannot convert 'ColourSensor::routine0' from type 'void (ColourSensor::)()' to type 'void (*)()'

Upvotes: 1

Views: 216

Answers (1)

bolov
bolov

Reputation: 75854

Things get complicated because they are methods. A method has an implicit hidden this parameter, so it's a different type than a free functions.

This is the correct syntax:

class ColourSensor
{
public:
    using Routine = void (ColourSensor::*)();

    Routine routines[3] = {
        &ColourSensor::routine0,
        &ColourSensor::routine1,
        &ColourSensor::routine2,
    };

    void routine0();
    void routine1();
    void routine2();

    void test()
    {
        // calling syntax:
        (this->*routines[0])();
    }  
};

An alternative which simplifies the calling syntax using non-capturing lambdas (which can decay to function pointer):

class ColourSensor
{
public:
    using Routine = void (*)(ColourSensor*);

    Routine routines[3] = {
        [](ColourSensor* cs) { return cs->routine0(); },
        [](ColourSensor* cs) { return cs->routine1(); },
        [](ColourSensor* cs) { return cs->routine2(); }
    };

    void routine0();
    void routine1();
    void routine2();

    void test()
    {
        // simpler calling syntax
        routines[0](this);
    }  
};

Going one step further (and off the rails), if you know you always use this object to call the methods you need to capture this in lambda. The problem now is that capturing lambdas can't decay to function pointers and since every lambda is a different type you can't store them in an array. The usual solution in C++ is type erasure with std::function. Unfortunately arduino C++ environment doesn't have the C++ standard library (because of the size constraints). For reference, this is how it would have looked (and since we are using the standard library, we use std::array):

/// !! not working in Arduino !!

#include <functional>
#include <array>

class ColourSensor
{
public:
    std::array<std::function<void(void)>, 3> routines = {
        [this]() { return this->routine0(); },
        [this]() { return this->routine1(); },
        [this]() { return this->routine2(); }
    };

    void routine0();
    void routine1();
    void routine2();

    void test()
    {
        // simple calling syntax
        routines[0]();
    }  
};

There is a workaround for this. And although it's a bit of work to setup, it's actually faster than the std::function because we don't use type erasure:

class ColourSensor;

class Routine
{
private:
    using R = void (ColourSensor::*)();
    R routine_;
    ColourSensor* calling_obj_;

public:
    Routine(R routine, ColourSensor* calling_obj)
        : routine_{routine}, calling_obj_{calling_obj}
    {}

    void operator()();
};

class ColourSensor
{
public:
    Routine routines[3] = {
        Routine{&ColourSensor::routine0, this},
        Routine{&ColourSensor::routine1, this},
        Routine{&ColourSensor::routine2, this}
    };

    void routine0();
    void routine1();
    void routine2();

    void test()
    {
        // simple calling syntax
        routines[0]();
    }  
};

void Routine::operator()() { return (calling_obj_->*routine_)(); }

If however your routines don't use the state of the ColourSensor, than you can make them static functions:

class ColourSensor
{
public:
    using Routine = void (*)();

    Routine routines[3] = {
        routine0,
        routine1,
        routine2,
    };

    static void routine0();
    static void routine1();
    static void routine2();

    void test()
    {
        routines[0]();
    }  
};

Upvotes: 4

Related Questions