-
-
Save caiorss/7db3de56dea0c502c6f749293b5013ef to your computer and use it in GitHub Desktop.
Experiment - passing lambdas and fuctors to C function pointer callbacsk
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
// Function without context pointer | |
typedef void (*callback_t) (int n); | |
typedef void (*callback_closure_t) (int n, void* context); | |
/** Function with C-callback argument without context pointer. */ | |
void dotimes_version1(int size, callback_t fun) | |
{ | |
printf(" [TRACE] <ENTRY> Called function: %s \n", __FUNCTION__); | |
for(int i = 0; i < size; i++){ fun(i); } | |
printf(" [TRACE] <EXIT> Called function: %s \n", __FUNCTION__); | |
} | |
/** Function with C-callback argument with context pointer for passing function state to | |
* to the function pointer. | |
*/ | |
void dotimes_version2(int size, callback_closure_t fun, void* context) | |
{ | |
printf(" [TRACE] <ENTRY> Called function: %s \n", __FUNCTION__); | |
for(int i = 0; i < size; i++){ fun(i, context); } | |
printf(" [TRACE] <EXIT> Called function: %s \n", __FUNCTION__); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cmake_minimum_required(VERSION 3.9) | |
project(cpp-lambda-test) | |
#========== Global Configurations =============# | |
#----------------------------------------------# | |
set(CMAKE_CXX_STANDARD 17) | |
set(CMAKE_VERBOSE_MAKEFILE ON) | |
#========== Targets Configurations ============# | |
add_library( clib SHARED clib.c) | |
add_executable( consumer consumer.cpp) | |
target_link_libraries( consumer clib) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <functional> // std::function container | |
#include <cassert> | |
using callback_t = void (*) (int n); | |
using callback_closure_t = void (*) (int n, void* context); | |
// Functions with C-linkage | |
#define EXTERN_C extern "C" | |
/** Provided by the shared library (aka shared object on Unix-like systems) */ | |
EXTERN_C void dotimes_version1(int size, callback_t fun); | |
EXTERN_C void dotimes_version2(int size, callback_closure_t fun, void* context); | |
using FunctionCallback1 = std::function<void (int )>; | |
/** Namespace contains workarounds for passing capturing lambdas | |
* to function dotimes_version (C function without context void pointer) | |
* | |
* Note: This solution is not thread-safe. The most suitable workaround for | |
* for passing capturing lambdas or C++ functors to C-callbakcs is to | |
* redesign it for using a context void pointer, that allows passing state | |
* to the C-callback. | |
*----------------------------------------------------------------------*/ | |
namespace Workaround1 { | |
/* This function encapsulates callback global variable | |
** for avoiding the global-initialization fiasco. | |
*-----------------------------------------------------*/ | |
auto get_callback() -> FunctionCallback1& | |
{ | |
// Global variable, its lifetimes corresponds to the program lifetime. | |
static FunctionCallback1 callback; | |
return callback; | |
}; | |
/** Set global variable callback */ | |
auto set_callback(FunctionCallback1 func) -> void | |
{ | |
auto& callback = get_callback(); | |
callback = func; | |
} | |
void workaround1_callback_adapter(int n) | |
{ | |
get_callback()(n); | |
} | |
/* Disadvantage: This solution is not thread-safe and requires locks | |
* to protect the callback global state. Only one callback can be | |
* passed per thread. | |
*/ | |
void wrapper_to_dotimes_version1(int size, FunctionCallback1 func) | |
{ | |
set_callback(func); | |
dotimes_version1(size, &workaround1_callback_adapter); | |
} | |
} | |
struct FunctionObject | |
{ | |
int counter = 10; | |
FunctionObject(){ } | |
FunctionObject(int cnt): counter(cnt){} | |
void operator()(int n) | |
{ | |
std::printf(" [FUNCTOR ] n = %d / counter = %d \n", n, counter++); | |
} | |
}; | |
void FunctionObject_adapter(int n, void* context) | |
{ | |
assert(context != nullptr); | |
// Note: C-style cast also works, but prefer C++-style cast. | |
FunctionObject* pFunctor = static_cast<FunctionObject*>(context); | |
// [Also works in this way =>>] pFunctor->operator()(n); | |
(*pFunctor)(n); | |
} | |
int main(int argc, char** argv) | |
{ | |
std::puts(" [INFO] Consumer started OK."); | |
/* EXPERIMENT 1 - Passing non-capturing lambda to C-function pointer callbacks without | |
* void context pointer. | |
*/ | |
std::puts("\n ===== [EXPERIMENT 1] Passing non-capturing lambdas ===============\n"); | |
{ | |
/** Non-capturing lambdas are converted to function pointers. */ | |
dotimes_version1(5, [](int n){ | |
std::printf(" [EXPERIMENT 1] n = %d \n", n); | |
}); | |
} | |
/* EXPERIMENT 2 - Passing capturing lambda to C-function pointer callbacks without | |
* void context pointer. | |
* | |
* This experiment fails as capturing lambdas cannot be converted to function-pointers. | |
*/ | |
std::puts("\n ===== [EXPERIMENT 2] Passing Capturing lambdas <FAILURE> ========= \n"); | |
{ | |
int counter = 10; | |
auto lamb = [&counter](int n){ | |
std::printf(" [EXPERIMENT 2] n = %d / counter = %d \n", n, counter++); | |
}; | |
/* COMPILE-TIME-ERROR: Non-capturing lambdas cannot be passed to | |
* to function-pointers !!! | |
* Remove the next comment ('//') in order to see the compile-time error: | |
* | |
* Error: | |
* --------------------------------------------------------------- | |
* | |
* [build] cpp-lambda-c/consumer.cpp:30:3: error: no matching function for call to 'dotimes_version1' | |
* [build] dotimes_version1(5, lamb); | |
* [build] ^~~~~~~~~~~~~~~~ | |
* [build] consumer.cpp:7:17: note: candidate function not viable: no known conversion from '(lambda at /home/mxpkf8/temp-projects/cpp-lambda-c/consumer.cpp:26:15)' to 'callback_t' (aka 'void (*)(int)') for 2nd argument | |
* [build] extern "C" void dotimes_version1(int size, callback_t fun); | |
* [build] ^ | |
****************************************************************/ | |
// Change from '0' to '1' to enable the compile-time error. | |
#if 0 | |
// => Compile-time error! | |
dotimes_version1(5, lamb); | |
#endif | |
} | |
/* EXPERIMENT 3 - Passing capturing lambda to C-function pointer callbacks without | |
* void context pointer using a global-state workaround. | |
*/ | |
std::puts("\n ===== [EXPERIMENT 3] Passing Capturing lambdas - Workaround 1 =====\n"); | |
{ | |
int counter = 10; | |
auto lamb = [&counter](int n){ | |
std::printf(" [EXPERIMENT 3] n = %d / counter = %d \n", n, counter++); | |
}; | |
std::puts("\n --->> Passing C++ capturing lambda "); | |
Workaround1::wrapper_to_dotimes_version1(5, lamb); | |
std::puts("\n --->> Passing function-object (aka C++ Functor) "); | |
Workaround1::wrapper_to_dotimes_version1(5, FunctionObject(25)); | |
} | |
/* EXPERIMENT 4 - Passing capturing lambda to C-function pointer callbacks with | |
* void context pointer. | |
*/ | |
std::puts("\n ===== [EXPERIMENT 4] Passing Functors to capturing lambda ==\n"); | |
{ | |
std::puts("\n --->> Passing function-object (aka C++ Functor) [APPROACH 1] "); | |
FunctionObject obj1(26); | |
dotimes_version2(5, &FunctionObject_adapter, &obj1); | |
// Note: This lambda can only be passed due to it be non-capturing. | |
auto adapter_for_FunctionObject = [](int n, void* context) | |
{ | |
assert(context != nullptr && "Context pointer should not be nullptr."); | |
FunctionObject* pFunctor = reinterpret_cast<FunctionObject*>(context); | |
pFunctor->operator()(n); | |
}; | |
std::puts("\n --->> Passing function-object (aka C++ Functor) [APPROACH 2] "); | |
FunctionObject obj2; | |
dotimes_version2(5, adapter_for_FunctionObject, &obj2); | |
} | |
/* EXPERIMENT 5 - Passing capturing lambda to C-function pointer callbacks with | |
* void context pointer. | |
*/ | |
std::puts("\n ===== [EXPERIMENT 5] Passing Capturing lambdas <APPROACH 1> ==\n"); | |
{ | |
using FunctionCallback2 = std::function<void (int size)>; | |
auto adpter_for_lambda = [](int n, void* context) | |
{ | |
assert(context != nullptr && "Context pointer (state) should not be null."); | |
FunctionCallback2* pFunc = reinterpret_cast<FunctionCallback2*>(context); | |
(*pFunc)(n); | |
}; | |
int counter = -100; | |
auto callback_lambda = [&counter](int n){ | |
std::printf(" [EXPERIMENT 5] n = %d / counter = %d \n", n, counter++); | |
}; | |
FunctionCallback2 callback_object = callback_lambda; | |
std::puts("\n --->> Passing capturing lambda [APPROACH 1 - Type erasure] --- "); | |
dotimes_version2(5, adpter_for_lambda, &callback_object); | |
} | |
std::puts("\n\n [INFO] System shutdown gracefully Ok."); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[INFO] Consumer started OK. | |
===== [EXPERIMENT 1] Passing non-capturing lambdas =============== | |
[TRACE] <ENTRY> Called function: dotimes_version1 | |
[EXPERIMENT 1] n = 0 | |
[EXPERIMENT 1] n = 1 | |
[EXPERIMENT 1] n = 2 | |
[EXPERIMENT 1] n = 3 | |
[EXPERIMENT 1] n = 4 | |
[TRACE] <EXIT> Called function: dotimes_version1 | |
===== [EXPERIMENT 2] Passing Capturing lambdas <FAILURE> ========= | |
===== [EXPERIMENT 3] Passing Capturing lambdas - Workaround 1 ===== | |
--->> Passing C++ capturing lambda | |
[TRACE] <ENTRY> Called function: dotimes_version1 | |
[EXPERIMENT 3] n = 0 / counter = 10 | |
[EXPERIMENT 3] n = 1 / counter = 11 | |
[EXPERIMENT 3] n = 2 / counter = 12 | |
[EXPERIMENT 3] n = 3 / counter = 13 | |
[EXPERIMENT 3] n = 4 / counter = 14 | |
[TRACE] <EXIT> Called function: dotimes_version1 | |
--->> Passing function-object (aka C++ Functor) | |
[TRACE] <ENTRY> Called function: dotimes_version1 | |
[FUNCTOR ] n = 0 / counter = 25 | |
[FUNCTOR ] n = 1 / counter = 26 | |
[FUNCTOR ] n = 2 / counter = 27 | |
[FUNCTOR ] n = 3 / counter = 28 | |
[FUNCTOR ] n = 4 / counter = 29 | |
[TRACE] <EXIT> Called function: dotimes_version1 | |
===== [EXPERIMENT 4] Passing Functors to capturing lambda == | |
--->> Passing function-object (aka C++ Functor) [APPROACH 1] | |
[TRACE] <ENTRY> Called function: dotimes_version2 | |
[FUNCTOR ] n = 0 / counter = 26 | |
[FUNCTOR ] n = 1 / counter = 27 | |
[FUNCTOR ] n = 2 / counter = 28 | |
[FUNCTOR ] n = 3 / counter = 29 | |
[FUNCTOR ] n = 4 / counter = 30 | |
[TRACE] <EXIT> Called function: dotimes_version2 | |
--->> Passing function-object (aka C++ Functor) [APPROACH 2] | |
[TRACE] <ENTRY> Called function: dotimes_version2 | |
[FUNCTOR ] n = 0 / counter = 10 | |
[FUNCTOR ] n = 1 / counter = 11 | |
[FUNCTOR ] n = 2 / counter = 12 | |
[FUNCTOR ] n = 3 / counter = 13 | |
[FUNCTOR ] n = 4 / counter = 14 | |
[TRACE] <EXIT> Called function: dotimes_version2 | |
===== [EXPERIMENT 5] Passing Capturing lambdas <APPROACH 1> == | |
--->> Passing capturing lambda [APPROACH 1 - Type erasure] --- | |
[TRACE] <ENTRY> Called function: dotimes_version2 | |
[EXPERIMENT 5] n = 0 / counter = -100 | |
[EXPERIMENT 5] n = 1 / counter = -99 | |
[EXPERIMENT 5] n = 2 / counter = -98 | |
[EXPERIMENT 5] n = 3 / counter = -97 | |
[EXPERIMENT 5] n = 4 / counter = -96 | |
[TRACE] <EXIT> Called function: dotimes_version2 | |
[INFO] System shutdown gracefully Ok. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment