0

I have a class with a member variable std::function customCallback, and I need to let the user customize its behavior. customCallback should take as input a const std::array<int, N>& and a int, and return a std::array<int, N> (where N is a template parameter).

I am currently using std::bind to achieve this, but I can't understand why the compilation fails. What am I missing?

Since the original code involves inheritance and templates, I am including inheritance and templates in my minimal reproducible example (live code here) as well.

The user should be able to use both the constructor OR a member function to set the custom behavior.

base.h:

#include <array>
#include <functional>

template <std::size_t N>
class BaseClass {
public:
    virtual std::array<int, N> CustomBehavior(const std::array<int, N>& user_array, int user_number) = 0;    
protected:
    std::array<int, N> my_array = {0, 0};
};

derived.h:

#include <base.h>
#include <cstddef>

template <std::size_t N>
class DerivedClass : public BaseClass<N> {
public:
    DerivedClass() = default;

    DerivedClass(std::function<std::array<int, N>(const std::array<int, N>&, int)> custom_f)
        : customCallback(std::bind(custom_f, std::ref(std::placeholders::_1), std::placeholders::_2)) {}

    void SetCustomBehavior(std::function<std::array<int, N>(const std::array<int>&, int)> custom_f) {
        customCallback = std::bind(custom_f, std::ref(std::placeholders::_1), std::placeholders::_2);
    }

    std::array<int, N> CustomBehavior(const std::array<int, N>& user_array, int user_number) override {
        if (customCallback)
            this->my_array = customCallback(user_array, user_number);
        return this->my_array;
    }

private:
    std::function<std::array<int, N>(const std::array<int, N>&, int)> customCallback;
};

main.cpp:

#include <derived.h>
#include <cassert>

static constexpr std::size_t MySize = 2;

std::array<int, MySize> my_behavior(const std::array<int, MySize>& input_array, int a) {
    return {a * input_array[0], a * input_array[1]};
}

int main() {

    std::array<int, MySize> my_array = {1, 1};

    // Default constructor (COMPILES)
    DerivedClass<MySize> foo_1; // OK
    std::array<int, MySize> bar_1 = foo_1.CustomBehavior(my_array, 2);
    assert(bar_1[0] == 0 && bar_1[1] == 0);

    // Custom constructor (ERROR)
    DerivedClass<MySize> foo_2(my_behavior); // COMPILATION ERROR
    std::array<int, MySize> bar_2 = foo_2.CustomBehavior(my_array, 2);
    assert(bar_2[0] == 2 && bar_2[1] == 2);

    // Custom behavior set later on (ERROR)
    DerivedClass<MySize> foo_3;  // OK
    foo_3.SetCustomBehavior(my_behavior); // COMPILATION ERROR
    std::array<int, MySize> bar_3 = foo_3.CustomBehavior(my_array, 2);
    assert(bar_3[0] == 2 && bar_3[1] == 2);

    return 0;
}

I am not including the whole compilation error since it's fairly long, but it can be seen live code here.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
giacomo-b
  • 394
  • 3
  • 13

1 Answers1

3

Well, for starters, the first error you are getting is an error on std::array<int> in Derived::SetCustomBehavior():

error: wrong number of template arguments (1, should be 2)

You also have an error on std::ref(std::placeholders::_1), it should be just std::placeholders::_1.

Fixing those two mistakes, the code then compiles and runs (using corrected asserts):

Online Demo

That being said, you don't actually need std::bind() at all in this situation. Your custom_f parameter is the same type as your customCallback member, so simply assign custom_f as-is to customCallback.

And do yourself a favor - create type aliases for your std::function and std::array types to make your code more readable.

Try this:

base.h

#include <array>
#include <functional>
#include <cstddef>

template<std::size_t N>
using intArray = std::array<int, N>;

template<std::size_t N>
using callbackType = 
std::function<intArray<N>(const intArray<N>&, int)>;

template <std::size_t N>
class BaseClass {
public:
    virtual intArray<N> CustomBehavior(const intArray<N>& user_array, int user_number) = 0;
protected:
    intArray<N> my_array = {0, 0};
};

derived.h:

#include "base.h"

template <std::size_t N>
class DerivedClass : public BaseClass<N> {
public:
    DerivedClass() = default;

    DerivedClass(callbackType<N> custom_f)
        : customCallback(std::move(custom_f))
    {}

    void SetCustomBehavior(callbackType<N> custom_f) {
        customCallback = std::move(custom_f);
    }

    intArray<N> CustomBehavior(const intArray<N>& user_array, int user_number) override {
        if (customCallback)
            this->my_array = customCallback(user_array, user_number);
        return this->my_array;
    }

private:
    callbackType<N> customCallback;
};

main.cpp:

#include "derived.h" 
#include <cassert>

static constexpr std::size_t MySize = 2;
using myIntArray = intArray<MySize>;

myIntArray my_behavior(const myIntArray& input_array, int a) {
    return {a * input_array[0], a * input_array[1]};
}

int main() {
    myIntArray my_array = {1, 1};

    // Default constructor
    DerivedClass<MySize> foo_1;

    myIntArray bar_1 = foo_1.CustomBehavior(my_array, 2);
    assert(bar_1[0] == 0 && bar_1[1] == 0);

    // Custom constructor
    DerivedClass<MySize> foo_2(my_behavior);
    myIntArray bar_2 = foo_2.CustomBehavior(my_array, 2);
    assert(bar_2[0] == 2 && bar_2[1] == 2);

    // Custom behavior set later on
    DerivedClass<MySize> foo_3;
    foo_3.SetCustomBehavior(my_behavior);
    myIntArray bar_3 = foo_3.CustomBehavior(my_array, 2);
    assert(bar_3[0] == 2 && bar_3[1] == 2);

    return 0;
}

Online Demo

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • The first assert will fail since `std::function` is empty at first. – 康桓瑋 Sep 27 '21 at 14:41
  • @康桓瑋 fixed, thanks – Remy Lebeau Sep 27 '21 at 14:59
  • Thank you! I have been struggling with this for an hour and had no idea you could directly assign functions like that. I am indeed using aliases in the original code, for my mental sanity. I was just trying to be more explicit here. – giacomo-b Sep 27 '21 at 15:27
  • @康桓瑋 thank you for pointing that out, I wash in a rush. I fixed it in the original question as well. – giacomo-b Sep 27 '21 at 15:28
  • On a side note, do you know why the version using `std::bind` wouldn't work? I get that it's unnecessarily complex, but how would I go about fixing it? – giacomo-b Sep 27 '21 at 15:30
  • 1
    @giacomo-b I updated my answer to address that. – Remy Lebeau Sep 27 '21 at 15:55
  • @RemyLebeau thank, I will try that right away. The reason I was using `std::ref` is that [this answer](https://stackoverflow.com/a/31811096/5617293) mentions that using `std::bind` copies the arguments if `std::ref` is not used. – giacomo-b Sep 27 '21 at 15:59