0

In the following source code, init_data_func() is declared a friend function to the class Manger.Therefore, whenever I want to pass a function as an argument to Manager::init_data(), it must have the name 'init_data_func.'

How can I use an arbitrary name for the function and pass it as a parameter to Manager::init_data()?

#include <iostream>

class Manager {
private:
    int n_;
    int *a_, *b_, *c_;
    
public:
    Manager(int n) : n_(n) {
        a_ = new int[n];
        b_ = new int[n];
        c_ = new int[n];
    }
    ~Manager() {
        delete[] a_;
        delete[] b_;
        delete[] c_;
    }
    friend void init_data_func(Manager& manager);
    void init_data(void (*init_data_func)(Manager&)) {
        init_data_func(*this);
    }
};

void init_data_func(Manager& manager) {
    for (int i = 0; i < manager.n_; ++i) {
        manager.a_[i] = rand();
        manager.b_[i] = rand();
    }
}

int main() {
    const int N = 1000;
    Manager manager(N);

    // Initialize data using init_data_func
    manager.init_data(&init_data_func);

    return 0;
}
user366312
  • 16,949
  • 65
  • 235
  • 452
  • Warning: [The Rule of Three](https://stackoverflow.com/q/4172722/4581301) has not been observed by `Manager`, turning the class into a time-bomb. – user4581301 Mar 31 '23 at 23:36
  • @user4581301, fucus on the given problem. – user366312 Mar 31 '23 at 23:37
  • 1
    Arbitrary names do not exist in C++. You can play conditional compilation or linking games with multiple definitions of the same function name that get conditionally turned on or off so that no more than one definition exists in the program when it's built. You can also use overloads, but that won't work with the `friend` because they will all have different signatures. I'm interested in seeing what people come up with as answers for this. – user4581301 Mar 31 '23 at 23:41
  • You may have to flip this around into more of a factory design where the decisions are made and processed ahead of the constructor and the results are passed into the constructor or change `void init_data_func(Manager& manager);` into `void init_data_func(int * a, int * b, int * c);` (doesn't scale well) or `void init_data_func(ManagerView & managerview);` where `ManagerView` is the `friend` and gives a tightly controlled and temporary window into the internals of `Manager`. – user4581301 Mar 31 '23 at 23:55
  • 1
    You could have an abstract `builder` class that is a friend, and subclass it to provide different initialisation methods? – Den-Jason Mar 31 '23 at 23:58
  • Builder. That's the word I was looking for. Factory;'s something else entirely. – user4581301 Mar 31 '23 at 23:58
  • Anyway, the gist is you can't do what you want to do and need to insert a layer of abstraction somewhere in order to get the behaviour you want. – user4581301 Apr 01 '23 at 00:01
  • *You can pass* a function whatever is its name to manager.init_data(), there is nothing to change in your code!! Except of course, the rules of three issue. – dalfaB Apr 01 '23 at 00:06
  • What do you mean by an "arbitrary name"? Any function with the correct signature (non-member, return type `void`, single argument that is a `Manager &`) can be passed as an argument to `Manager::init_data`. Define a function `void foo(Manager &x) {}` before `main()` and `main()` will be able to do `manager.init_data(foo)` quite happily. `foo()` can only access any `public` members (or call `public` member functions) of `Manager` unless `foo()` is declared as a `friend` [passing it to `init_data()` does not grant it friendship]. – Peter Apr 01 '23 at 00:14

2 Answers2

1

You could use the Builder pattern, see https://godbolt.org/z/M7e6eP4aa

Essentially you declare an abstract builder class to be a friend of the manager. But only the abstract builder can access the members, so you have to provide accessors in the abstract builder to be used by a real builder. An alternative approach could be for the abstract builder to provide an iterator.

Note there are better methods of seeding a vector with random numbers than what is shown here (generator expression)

    #include <iostream>
    #include <vector>

    struct Manager {
        friend class ManagerBuilder;

        Manager() = delete;
        Manager(std::size_t n) : n_(n) {
            a_.resize(n);
            b_.resize(n);
            c_.resize(n);
        }
        ~Manager() = default;

    protected:
        std::size_t n_;
        std::vector<int> a_;
        std::vector<int> b_;
        std::vector<int> c_;
    };

    struct ManagerBuilder {
        ManagerBuilder(Manager& inst) : mgr_(inst) {}

        virtual ~ManagerBuilder() = default;
        virtual void initialiseManager() = 0;

    protected:
        Manager& mgr_;
        std::size_t nItems() {
            return mgr_.n_;
        }

        std::vector<int>& listA() {
            return mgr_.a_;
        }

    };

    struct RandomManagerBuilder : public ManagerBuilder {
        RandomManagerBuilder(Manager& inst) : ManagerBuilder(inst) {}
        ~RandomManagerBuilder() override {}
        void initialiseManager() override {
            std::vector<int>& inst_a = listA();
            for (int i = 0; i < nItems(); ++i) {
                inst_a[i] = rand();
            }
        }
    };



    int main() {
        const int N = 1000;
        Manager manager(N);

        RandomManagerBuilder bldr(manager);

        return 0;
    }

A completely different approach would be to make a std::function a friend - see this answer for detail - https://stackoverflow.com/a/21647196/1607937

Den-Jason
  • 2,395
  • 1
  • 22
  • 17
0

This seems like a good place for std::function and lambdas.

    void init_data(std::function<void(Manager&)> init_data_func) {
        init_data_func(*this);
    }

And then:

int main() {
    const int N = 1000;
    Manager manager(N);

    // Initialize data using init_data_func
    manager.init_data([](Manager& manager) {  
        for (int i = 0; i < manager.n_; ++i) {
             manager.a_[i] = rand();
             manager.b_[i] = rand();
        }
    });

    return 0;
}

Using this you can create any lambda you want, but they will not be friends of Manager and so you'll have to work with that class interface.

Chris
  • 26,361
  • 5
  • 21
  • 42
  • This doesn't work. – user366312 Mar 31 '23 at 23:35
  • init_data2.cpp: In lambda function: init_data2.cpp:42:37: error: 'int Manager::n_' is private within this context 42 | for (int i = 0; i < manager.n_; ++i) { | ^~ init_data2.cpp:6:9: note: declared private here 6 | int n_; | ^~ init_data2.cpp:43:22: error: 'int* Manager::a_' is private within this context 43 | manager.a_[i] = rand(); | ^~ init_data2.cpp:7:10: note: declared private here 7 | int *a_, *b_, *c_; | ^~ init_data2.cpp:44:22: error: – user366312 Mar 31 '23 at 23:36