This is an idea I thought of that maybe I could apply to my C project that currently interfaces with some C++ objects through opaque pointers. That's the usual way to do it, but dynamic allocation is unavoidable.
The following code shows my idea by using std::mt19937 as an example. No dynamic allocation is needed, but some #ifdefs might be required for different language implementations.
// C interface
typedef struct {
// match sizeof and alignof std::mt19937
// `#ifdef`s might be required
alignas(8) char raw[5000];
} t_rand;
void rand_init(t_rand *, uint32_t);
void rand_fin(t_rand *);
uint32_t rand_gen_uint32(t_rand *);
// C++ implementation
#include <memory>
#include <random>
void rand_init(t_rand *r, uint32_t seed) {
static_assert(
sizeof(t_rand) == sizeof(std::mt19937)
&& alignof(t_rand) == alignof(std::mt19937));
std::construct_at(reinterpret_cast<std::mt19937 *>(r), seed);
}
void rand_fin(t_rand *r) {
std::destroy_at(reinterpret_cast<std::mt19937 *>(r));
}
uint32_t rand_gen_uint32(t_rand *r) {
return (*reinterpret_cast<std::mt19937 *>(r))();
}
Is this a doable approach? If not, what are the problems, and what would be a better way? If it's okay, I'd also like to know if there's an actual project that uses the same pattern.
BTW, I think it was the wrong decision to set result_type or std::mt19937 as uint_fast32_t, which is usually uint64_t on 64-bit platforms, to avoid zero/sign-extension. It should have been uint_least32_t to consume less memory and provide better L1 cache hits.