ETLCPP / etl

Embedded Template Library
https://www.etlcpp.com
MIT License
2.14k stars 381 forks source link

Feature Request: Implement std::bind-like functionality for binding member functions with state #679

Open GeorgeDeac opened 1 year ago

GeorgeDeac commented 1 year ago

I would like to propose the addition of a utility similar to std::bind from the C++ std. I believe it could be useful to many users as this would allow them to create custom function objects with bound arguments, providing a convenient way to adapt function signatures and pass additional data to callback functions.

A specific use case that inspired this request is a scenario where a user wants to pass stateful functions (e.g., member functions of an object) to another function, which expects a specific function signature.

Example:

Suppose we have a class MyCipher with an internal state:


class MyCipher {
public:
  MyCipher(int key) : key_(key) {}

  void encrypt(byte* output, const byte* input, size_t len) {
    // Perform encryption with the internal key_
  }

private:
  int key_;
  // + Other changing state
};

Now, we have a window function that needs to accept both stateful or stateless objects (to apply our cipher in byte chunks etc):

// Template function that accepts any callable object with the specified signature
template <typename Callable>
void chunkApply(byte* output, const byte* input, size_t len, Callable func) {
    // ... More Logic
    // Apply the function on the current chunk
    func(output, currentChunk, currentChunkLength);
    // ...
}

Ideally we would just do something like:

MyCipher cipher(/*blabla*/);
chunkApply(output, input, len, etl::bind(&MyCipher::encrypt, &cipher));

But currently we need to do:


// Helper Impl function with common body
template <typename Func, typename... Args>
void chunkApplyImpl(byte* output, const byte* input, size_t len, Func func, Args... args) {
  // ... More Logic
  // Process the data using the provided function and the additional arguments
  func(output, currentChunk, currentChunkLength, args ...);
  // ...
}

// Stateless version overload
void chunkApply(byte* output, const byte* input, size_t len, void (*func)(byte*, const byte*, size_t)) {
  chunkApplyImpl(output, input, len, func);
}

// Stateful version overload
void chunkApply(byte* output, const byte* input, size_t len, void (*func)(byte*, const byte*, size_t, MyCipher*), MyCipher* cipher) {
  chunkApplyImpl(output, input, len, func, cipher);
}

// Wrapper to adapt class function signature (in my case this is more complex)
void encryptWrapper(byte* output, const byte* input, size_t len, MyCipher* cipher) {
  cipher->encrypt(output, input, len);
}

// ...

MyCipher cipher(/*blabla*/);
chunkApply(output, input, len, encryptWrapper, &cipher);

And the boiler plate increases with the number of methods we want to pass into this pattern.

jwellbelove commented 1 year ago

I will look into what is require of std::bind. It will depend on whether it can be implemented without access to compiler specific implicit or built-in functions.