0

I am doing a challenge for myself and writing a program in C++ without using classes and class related machinery. Additional conditions are I can use everything from stl and have full type safety, meaning no raw pointers or casting from one type to another. I am now in a situation where I'm not sure how to proceed with the constraints I have put on myself.

Problem: I want to create a std::vector of functions, but each of those functions might take a different data type, and operate on that type only. Example:

struct DataAndFunction {
    AnyDataType data;
    std::function<void(AnyDataType&)> functionOperatingWithData;
};
...
std::vector<DataAndFunction> listOfFunctions;
...
for(auto& dataAndFunc : listOfFunctions) {
    dataAndFunc.functionOperatingWithData(dataAndFunc.data);
}

then there either would be different kind of AnyDataType and accompanying functions.

I know it could be solved in a couple of ways:

  1. with classes using polymorphism, where

    std::vector< DataAndFunction > listOfFunctions;

would just take a base class as a template parameter, and would have virtual method, that would be implemented by child classes, each with their own private data members, but the point of my challenge is to not use this pattern.

  1. I could pass void* as data and function signature, and inside each function I would cast the data to the appropriate type, but I want to use type safety and only smart pointers

I could also make DataAndFunction struct a generic by adding template parameter, but then how do I make a vector that could be filled with not just with of DataAndFunction<int> for example, but any template parameter?

And how would this problem be solved in a more functional style? How would a functional solution look in C++ that would operate on a list of functions each taking a different type of argument? Without using inheritance of course.

Or am I just asking how to emulate a virtual table?

Jarod42
  • 203,559
  • 14
  • 181
  • 302
t_smith
  • 89
  • 1
  • 1
  • 7
  • Possible duplicate : [C++14: Generic lambda with generic std::function as class member](https://stackoverflow.com/questions/47332305/c14-generic-lambda-with-generic-stdfunction-as-class-member) – François Andrieux Feb 05 '20 at 15:24
  • I think you should make constrains when you do understand how to deal with them. If you need functional why not to use functional language? – Slava Feb 05 '20 at 15:26
  • 1
    `DataAndFunction dint; DataAndFunction dchar; std::vector> funcs{[&](){ dint.f(dint.data); }, [&](){ dchar.f(dchar.data); }}; for(auto& f : funcs) { f(); }`? – Jarod42 Feb 05 '20 at 15:36
  • 1
    why not just make your vector hold `std::function` and capture your data in a lambda? – Alan Birtles Feb 05 '20 at 15:36
  • @Jarod42 That was the answer I was looking for and these lambdas where my missing piece. Thank you! If you wrote it as answer I could accept it. – t_smith Feb 05 '20 at 15:47
  • @t_smith if you constrained yourself to not using classes, how come you are fine with a capturing lambda? That's just syntactic sugar for a class with the captured values as member variables and a `operator()`. – Jesper Juhl Feb 05 '20 at 16:14
  • @JesperJuhl I specifically wanted to test an approach where data is defined separately from code, so I would have a application context variable that would store all data and fully describe the state. Then test how easy would it be to restore that application to any given state, and how easy it is to test and debug problems that way. – t_smith Feb 06 '20 at 06:51

3 Answers3

4

It seems you only need std::function<void()>:

DataAndFunction<int> dint;
DataAndFunction<char> dchar;
std::vector<std::function<void()>> funcs{
    [&](){ dint.f(dint.data); },
    [&](){ dchar.f(dchar.data); },
    [](){ std::cout << "Hello world\n"; }
};

for (auto& f : funcs) {
    f();
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
1

How you know, C++ is a strong typed language. That means you can't have a generic variable like others languages do like Python.

In C, the solution is to use a point to void (void*), but it is hard to manage and error-prone.

C++17 comes with 2 elegant solutions these you can use:

std::any and std::variant.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
TheArchitect
  • 1,160
  • 4
  • 15
  • 26
0

This is one way you could store a vector of functions that take different types as parameters, it also accepts a varying amount of parameters. It uses std::any, std::pair, and std::tuple to contain the function and its parameter(s). std::any is much safer than a void * however there is more memory overhead as std::any stores quite a bit of data.

the program simply outputs : "Hello World!"

#include <any>
#include <tuple>
#include <string>
#include <vector>
#include <utility>
#include <iostream>
#include <functional>

std::vector<std::pair<std::function<void(const std::any&)>, std::any>> funcList;

template <typename F, typename...T>
std::size_t insertAnyFunc(const F &func, const T&...params) {
    auto t = std::make_tuple<const T&...>(params...);
    funcList.push_back({
        [f = func](const std::any &a) {
            if constexpr (sizeof...(T) == 0)
                f();
            else
                std::apply(f, std::any_cast<const std::tuple<T...> &>(a));
        }, std::make_any<std::tuple<T...>>(t)
    });
    return funcList.size();
}

void execAnyFunc(const std::size_t &ID) {
    if (ID < funcList.size())
        funcList.at(ID).first(funcList.at(ID).second);
}

int main (void) {
    insertAnyFunc([](const std::string &s){ std::cout << s; }, std::string("Hello World!\n"));
    execAnyFunc(0);
    return 0;
}
pseudovella
  • 209
  • 1
  • 8