2

I'm storing a pointer to a function as a string:

//callback = std::function<void()> callback

std::stringstream cbPtrToString;
cbPtrToString << &callback;
std::string prtString = cbPtrToString.str();

How can I convert prtString back into a callable function? I've tried this below, but it doesn't do what I thought it would do.

std::stringstream stringToPtr(ptrString);
void* result;
stringToPtr >> result;
result();
Kmcgurty
  • 37
  • 1
  • 6
  • 1
    `result` is of type `void*`, it's not a callable object. BTW, what is the purpose of storing a (function) pointer into a string and extracting it back. – Daniel Langr Apr 12 '21 at 03:36
  • I figured I'd get asked, I guess it is pretty strange. I'm using the [cpp JSON](https://github.com/nlohmann/json) library to store a callback (and other information). This particular library doesn't support storing pointers, only strings and other basic types. Later in the program, I obviously would like to call the function. Coming from a JavaScript background, it makes sense to do it this way, unless I should be using a vector or something similar? – Kmcgurty Apr 12 '21 at 03:47
  • You can of course parse the pointer value from the string as a `uintptr_t` value, and then `reinterpret_cast` that as the appropriate pointer type. But you better be damn sure that what you cast it to is what the thing was originally, and in the case of your example storing a pointer to a `std::function`, you must ensure that object still exists. – paddy Apr 12 '21 at 03:51
  • 1
    Do you want to save and retrieve the function's address while the program is running? Trying this across different executions of the program might not work because the function's address can change due to [ASLR](https://en.wikipedia.org/wiki/Address_space_layout_randomization). – Blastfurnace Apr 12 '21 at 03:53
  • @Blastfurnace I see. It makes sense to not do it this way. How would you recommend I go about storing a function callback in a JSON-like object, then? Something like JavaScript or Lua would be easy, I just set the function at whatever index or key. CPP I'm at a loss. – Kmcgurty Apr 12 '21 at 03:59
  • Storing a pointer in a file and trying to reuse it in another program run is likely a very bad idea. Instead, I would, map the callbacks to some IDs and store these IDs in a file. – Daniel Langr Apr 12 '21 at 04:03
  • @Kmcgurty You haven't actually answered the question here... Are you storing these pointers only during your program's execution, and never storing them in files or otherwise passing into another process? – paddy Apr 12 '21 at 04:04
  • I'm not sure what you're alluding to in JS. `JSON.stringify` doesn't do anything with functions. You can `eval` a function if you store the source code of that function as text, but that's a different can of worms. It's very likely that there's a better way to achieve your overall use case, but we don't know what that is. – chris Apr 12 '21 at 04:06
  • 1
    If you want to call a function by name or identifier then [something like this](https://stackoverflow.com/questions/19473313/how-to-call-a-function-by-its-name-stdstring-in-c) might be the right direction. – Blastfurnace Apr 12 '21 at 04:07
  • @paddy My apologies, it's late where I am. I'm not actually storing the information into a JSON file, only as an object during runtime - I'm using the wrong terminology. Once the program quits I don't care about the data. – Kmcgurty Apr 12 '21 at 04:12
  • @Blastfurnace I believe that will work. Thank you! – Kmcgurty Apr 12 '21 at 04:16
  • You could go simple and create an array of function pointers. Use the **array index** (it's just a number) as the identifier or key. That should be safe and easier to implement. – Blastfurnace Apr 12 '21 at 04:17
  • 1
    @Kmcgurty -- So all of these functions have the same signature, i.e. return type and parameter types / number of parameters? – PaulMcKenzie Apr 12 '21 at 04:20
  • @PaulMcKenzie Yes, all of them will be void functions with no parameters. – Kmcgurty Apr 12 '21 at 04:21
  • @Kmcgurty OK. If they didn't then the solution would be you to have objects that overloaded `operator()`, where the parameters are actually part of the object. – PaulMcKenzie Apr 12 '21 at 04:22

1 Answers1

2

Following your example, you have std::function<void()> as your callback. I would first suggest you make that a typedef and some simple conversion functions:

typedef std::function<void()> CallbackType;

std::string CallbackToString(const CallbackType* cb)
{
    std::ostringstream oss;
    oss << reinterpret_cast<uintptr_t>(cb);
    return oss.str();
}

const CallbackType* StringToCallback(const std::string& str)
{
    std::istringstream iss(str);
    uintptr_t ptr;
    return (iss >> ptr) ? reinterpret_cast<CallbackType*>(ptr) : nullptr;
}

It is allowed by the C++ standard to convert from pointer types to uintptr_t and back, and provided these values are only used within the same process it is probably acceptable to do so.

You must ensure that your functions actually exist, however. In this live example, they're stored on the stack in main. However, if you were to create them as temporaries and then use those pointers after the objects had been destroyed, it would be bad.

If you don't want to be reinterpreting pointers, then as already suggested in the comments you could store some other kind of tokens such as keeping a table of all your callbacks and then just storing the table index. That would be considered a cleaner solution.

paddy
  • 60,864
  • 6
  • 61
  • 103
  • That answer is perfect, thank you. Is there anything specific I should be aware of that can change a function's address? Besides the obvious of restarting the program, or actually deleting the function. The main post comments suggest it's a bad idea to do it this way, but I believe that was just a miscommunication on my part of what I was trying to achieve. – Kmcgurty Apr 12 '21 at 04:50
  • 1
    Well, it would probably pay for you to learn the difference between [`std::function`](https://en.cppreference.com/w/cpp/utility/functional/function) and an _actual_ function. In any case, provided your object is not destroyed, its pointer is valid. Even if the OS decides to relocate your process in physical memory during its execution, the pointer remains valid. – paddy Apr 12 '21 at 04:55