0

I want to create a simple but generic callback with variadic parameters. I also need storing them, because callback will called later in time (from a different thread which uses the storage).

Here's what I have until now:

#include <iostream>
#include <string>

using namespace std;

void fileMgr_DoneWithOtherstuff_DoTheReads() {
  FROM SOMESTORAGE get the callback and params
  Do the reading
  string result = "CONTENT";
  Call the callback with result string and params
}

template<class ReadFileCallback, class ...T>
void fileMgr_ReadWithCallback(string filename, ReadFileCallback callback, T ...params) {
    cout << "fileMgr_ReadWithCallback is processing file: " << filename << endl;
//        string content = "CONTENT";
//        callback(content, params...);

      SOMESTORAGE = callback + params;
}


void readFileResult(string contents, int id) {
    cout << "readFileResult callback: contents=" << contents << ", id=" << id << endl;
}

void readFile(string filename, int id) {
    fileMgr_ReadWithCallback(filename, readFileResult, id);
}

int main()
{
    int fileId = 1;
    readFile("myfile", fileId);
    
    return 0;
}

But I don't want to call callback in fileMgr_ReadWithCallback, because it should be an async method, so I'd rather store the callback function and its parameters, and use it later once File Manager is able to perform the operation.

So the question is, what's the way to store ReadFileCallback and ...T into a struct maybe?

Or if there is better way doing this, let me know please.

I need the variadic arguments, as sometimes the extra param is just an int, but sometimes it can be 2 ints, or 1 string, etc, and I want to keep the FileManager generic.

I'm limited to C++11, but Boost is available.

Daniel
  • 2,318
  • 2
  • 22
  • 53
  • 2
    Does this answer your question? [How to bind variadic template params to function](https://stackoverflow.com/questions/21349339/how-to-bind-variadic-template-params-to-function) – t.niese Jul 21 '20 at 08:46
  • Do I have to use `bind`? Until now this code can work without function pointers and bind with pure templates. If possible I'd keep this. – Daniel Jul 21 '20 at 09:02
  • I don't think so, imho either need to use lambda or bind, and I don't think that this will make any difference. – t.niese Jul 21 '20 at 09:13
  • This seems too complicated. Can you please show some simple codes with bind or lambda? Storing a function and variadic arguments, and retrieving it + calling with the arguments retrieved – Daniel Jul 21 '20 at 09:21

2 Answers2

2

First of all, relying on a global variable is not a really good idea, because it will give wrong results if you call readFile while a previous read is still in progress. So you really should encapsulate that whole reading process in a class.

You need to use a lambda or std::bind because the parameters of callback are not known outside of the fileMgr_ReadWithCallback function.

So you create a wrapping callback accepting a std::string as a parameter. And use a lambda to capture the parameters, the lambda accepts a string as a parameter, and passes the string and the params to the callback:

storage = [=](std::string s) {
   callback(s, params...);
};

And that's how the code then could look like (but as I already said that's bad design)

#include <functional>
#include <iostream>


std::function<void(std::string)> storage;


void fileMgr_DoneWithOtherstuff_DoTheReads() {
  std::string result = "CONTENT";
  storage(result);
}

template<class ReadFileCallback, class ...T>
void fileMgr_ReadWithCallback(std::string filename, ReadFileCallback callback, T ...params) {
    std::cout << "fileMgr_ReadWithCallback is processing file: " << filename << std::endl;

    storage = [=](std::string s) {
        callback(s, params...);
    };
}

void readFileResult(std::string contents, int id) {
    std::cout << "readFileResult callback: contents=" << contents << ", id=" << id << std::endl;
}

void readFile(std::string filename, int id) {
    fileMgr_ReadWithCallback(filename, readFileResult, id);
}

int main()
{
    int fileId = 1;
    readFile("myfile", fileId);
    fileMgr_DoneWithOtherstuff_DoTheReads();
    
    return 0;
}

t.niese
  • 39,256
  • 9
  • 74
  • 101
1

You could store your parameters in a tuple via auto storedParams = std::make_tuple(params...). And later you can call your callback function simply with std::apply(callback, storedParams ).

To add an additional parameter (like content) you can use std::tuple_cat(std::make_tuple(content), storedParams ).

I would store the parameters and the callback separately.

BTW: std::apply is only available with C++17 - is there something to emulate that (perhaps in boost)? Otherwise I would try to use the implmentations for std::invoke and std::apply which are shown on cppreference (just remove constexpr...).

Here you can find the code: https://godbolt.org/z/z9MP3M

But you need to add std::apply (that is the most difficult part). Why you need to use such an old compiler?


Update

Perhaps you could use the type erasure features of std::function, see here:

#include <iostream>
#include <string>
#include <functional>


static std::function<void(std::string)> fileManagerStorage;

void fileMgr_DoneWithOtherstuff_DoTheReads() {
  //FROM SOMESTORAGE get the callback and params
  
  //Do the reading
  std::string content = "CONTENT";
  fileManagerStorage(content);

  //Call the callback with result string and params
  fileManagerStorage(content);
}

template<class ReadFileCallback, class ...T>
void fileMgr_ReadWithCallback(std::string filename, ReadFileCallback callback, T ...params) {
    std::cout << "fileMgr_ReadWithCallback is processing file: " << filename << std::endl;
//        string content = "CONTENT";
//        callback(content, params...);

    fileManagerStorage = [=](std::string content){
        callback(content, params...);
    };
}


void readFileResult(std::string contents, int id) {
    std::cout << "readFileResult callback: contents=" << contents << ", id=" << id << std::endl;
}

void readFile(std::string filename, int id) {
    fileMgr_ReadWithCallback(filename, readFileResult, id);
}

int main()
{
    int fileId = 1;
    readFile("myfile", fileId);
    
    return 0;
}

(You should store the storage not as a global variable - store it in your thread...)

Bernd
  • 2,113
  • 8
  • 22
  • But how would you store the callback for later use outside of the `fileMgr_ReadWithCallback` function, without losing the information what parameters it accepts? – t.niese Jul 21 '20 at 09:34
  • The shown code would only work if you call `fileMgr_DoneWithOtherstuff_DoTheReads` from within `fileMgr_ReadWithCallback`, and in that case the `Storage` object would be a bit of an overkill, as you could just pass a capturing lambda to `fileMgr_DoneWithOtherstuff_DoTheReads`. – t.niese Jul 21 '20 at 09:57
  • And you have show all relevant code within the answer and not as a link to an external site. – t.niese Jul 21 '20 at 09:57