0

I am looking to define a class member function once during runtime based on some conditional statements.

For example

void functionCandidate1(const int & arg1, Type1 & arg2) {// do something}
void functionCandidate2(const int & arg1, Type2 & arg2) {// do something different}

...

class MyClass{
    some_type class_function_;

    MyClass(const bool & use_function_candidate_1){
        if (use_function_candidate_1){
            class_function_ = functionCandidate1;
        } else {
            class_function_ = functionCandidate2;
        }
    }
};

The logic is my class will have some function class_function_ which will be called often, potentially with a different argument type. But this argument type will be defined upon constructing the class, so instead of checking an if continuously upon calling the function I'd like to assign it once in the beginning.

Note the content of functionCandidate1 and functionCandidate2 depends on what type arg2 has as this will be some object with a std::vector<StructType>. So this isn't solved by simple templating as the struct will contain members with different names.

Note also, this restriction is put on us by an external library and external repositories that we need to interface with, but have no control over.

Morten Nissov
  • 392
  • 3
  • 13
  • "...as the struct will contain members with different names." there are no members with different name in the code example. Its just one member that points to one of two functions – 463035818_is_not_an_ai Apr 27 '23 at 16:06
  • the two functions are actually function templates? – 463035818_is_not_an_ai Apr 27 '23 at 16:07
  • 1
    I think your biggest issue is that the functions do not have the same arguments. You could try making a member std::function and store a lambda in it. – Pepijn Kramer Apr 27 '23 at 16:07
  • Does this look like polymorphism to anyone else? I'm curious if you've considered using polymorphism here. – JohnFilleau Apr 27 '23 at 16:08
  • 4
    can you show an example of how you would use an isntance of this class? How do you plan to call `class_function_` when only at runtime the type of arguments is decided? – 463035818_is_not_an_ai Apr 27 '23 at 16:09
  • @JohnFilleau it does, certainly, but the two functions have different signature – 463035818_is_not_an_ai Apr 27 '23 at 16:09
  • this is a [xy problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). I am afraid you abstracted your actual problem so much that it is not recognisable anymore and the way you think would be a solution is none. The answer below is the best you can get for the question as asked. I suggest you to open a new question where you talk about the requirements not only about your idea of realising them – 463035818_is_not_an_ai Apr 27 '23 at 16:12
  • is it meaningful to link to this question in the new one I ask? – Morten Nissov Apr 27 '23 at 16:16
  • Also the functions are not function templates, the behavior changes fundamentally whether the argument is Type1 or Type2. The types are predefined, but it's only when the class is constructed I'll know which type it is – Morten Nissov Apr 27 '23 at 16:17
  • Wrap it into a this capturing lambda function on construction. – πάντα ῥεῖ Apr 27 '23 at 16:26
  • @MortenNissov "is it meaningful to link to this question in the new one I ask?" - It can't do any harm IMHO. – Jesper Juhl Apr 27 '23 at 16:27
  • @MortenNissov It looks like you want a base function class, one with a virtual `operator()`. Then you "call" the function by constructing the object with the parameters you would have passed, and then call `operator ()`. [See this question and answer](https://stackoverflow.com/questions/55930796/writing-a-generic-traverse-function-that-allows-the-flexibility-of-dealing-with) – PaulMcKenzie Apr 27 '23 at 16:29
  • Yes it is pretty mich OK to link. Now to the question. I assign specific values to unknows for better exposition. Say, let Type1 be `Pizza` and Type2 be `Helicopter`. How would you call `class_function_`? Suppose you can check its type at run time. Should each caller keep a pizza *and* a helicopter ready, just in case? Or maybe your usage scenario is different. Sone callers have pizzas and other helicopters, and they always interact with the right kind of `MyClass` object? – n. m. could be an AI Apr 27 '23 at 16:36
  • upon constructing the class you'll either be dealing with `Pizza` or `Helicopter`, and this is known to the class. It just needs to set the behavior accordingly. And the type will never change once it starts with one of them. – Morten Nissov Apr 27 '23 at 16:38
  • @MortenNissov: Yes, you can achieve it. See my answer below – Gurnet Apr 28 '23 at 05:55
  • I am not talking about `MyClass`. I am talking about the *users* of MyClass. What *they* should do when they get an instance of `MyClass`? – n. m. could be an AI Apr 29 '23 at 12:13

2 Answers2

1

Class member types need to be defined during compile time. You cannot have such a thing as a runtime dynamically typed member in C++.

Jesper Juhl
  • 30,449
  • 3
  • 47
  • 70
0

Of course your wish can be achieved.

You may use a combination of

  • std::any, which can store any function
  • a 'std::map', where you can associate a selector with a function
  • and a variadic parameter pack, which can be any kind and number of parameters

We will create a class and store all functions (any number) together with a selector in a std::map. We add functionality to the class, so that we can add an arbitrary number of functions.

We can also use a std::initializer_list for braced initialization of the class.

Then we add a "caller function" with a selector and call the wished function.

Please see the simple example below:

#include <iostream>
#include <map>
#include <string>
#include <any>
#include <utility>

class Caller
{
    std::map<int, std::any> selector;
public:
    Caller() : selector() {}

    Caller(std::initializer_list<std::pair<const int, std::any>> il) : selector(il) {}
    template<typename Function>
    void add(int key, Function&& someFunction) { selector[key] = std::any(someFunction); };

    template <typename ... Args>
    void call(int key, Args ... args) {
        if (selector.find(key) != selector.end()) {
            std::any_cast<std::add_pointer_t<void(Args ...)>>(selector[key])(args...);
        }
    }
};

// Some demo functions
void a(int x) {
    std::cout << "a\t" << x << '\n';
};
void b(int x, int y) {
    std::cout << "b\t" << x << '\t' << y << '\n';
};
void c(int x, int y, std::string z) {
    std::cout << "c\t" << x << '\t' << y << '\t' << z << '\n';
};
void d(std::string s, int x, int y, int z) {
    std::cout << "d\t" << s << '\t' << x << '\t' << y << '\t' << z << '\n';
};

// Definition of our caller map (using initializer list)
Caller caller{
    {1, a},
    {2, b},
    {3, c}, };

int main() {

    // Some demo calls
    caller.call(1, 1);
    caller.call(2, 1, 2);
    caller.call(3, 1, 2, std::string("3"));

    // Adding an additional function
    caller.add(4, d);

    // And call this as well.
    caller.call(4, std::string("ddd"), 1, 2, 3);
    return 0;
}

Translated to your needs this could be:

#include <iostream>
#include <string>
#include <unordered_map>
#include <any>
using namespace std::string_literals;

// Your functions
void functionCandidate1(int arg1, std::string arg2) { std::cout << "Function 1: " << arg1 << '\t' << arg2 << '\n'; }
void functionCandidate2(int arg1, long arg2) { std::cout << "Function 2: " << arg1 << '\t' << arg2 << '\n'; }

// The class
class MyClass {

    // Storage for function
    std::unordered_map<bool, std::any>   class_function_   {{true, functionCandidate1},{false, functionCandidate2}};
    // What function to use
    bool selector{};
public:
    // Constructor: Select function
    MyClass(const bool& use_function_candidate_1 = true) : selector(use_function_candidate_1) {}

    // Function caller
    template <typename ... Args>
    void call(Args ... args) {std::any_cast<std::add_pointer_t<void(Args ...)>>(class_function_[selector])(args...); }
};

int main() {
    // Test with default argument
    MyClass mc1{};
    mc1.call(1, "abc"s);

    // Test with true for function 1
    MyClass mc2{};
    mc2.call(2, "def"s);

    // Test with false for function 2
    MyClass mc3{false};
    mc3.call(3, 3L);
}
Gurnet
  • 118
  • 7