cbindgen
cbindgen copied to clipboard
() causes missing template arguments
#[repr(C)]
pub struct S<T, R> {
x: T,
y: R,
}
#[no_mangle]
pub extern "C" fn f() -> S<u32, ()> {
panic!()
}
generates
#include <cstdint>
#include <cstdlib>
template<typename T, typename R>
struct S {
T x;
R y;
};
extern "C" {
S<uint32_t> f();
} // extern "C"
which fails to compile with
out.cc:12:1: error: too few template arguments for class template 'S'
S<uint32_t> f();
^
out.cc:5:8: note: template is declared here
struct S {
^
Hi, I have a solution to this, that from
#[repr("C")]
pub struct S<T> {
x: T,
}
#[no_mangle]
pub extern "C" fn gives() -> *mut S<()> {
loop {}
}
#[no_mangle]
pub extern "C" fn takes(x: *mut S<()>) {}
generates
template<typename T = void>
struct S;
extern "C" {
S<void> *gives();
void takes(S<void> *x);
I am wondering however if using S<void>
is right here ? Are we sure that no weird void/()
behavior might happen ?
I would think this is ok, since we can only manipulate S
through a pointer; but I would like a more educated opinion on this 😅
Well i put some more thought into it, and I think there are 2 issues with this
- The original example would compile to
template<typename T, typename R>
struct S {
T x;
R y;
};
extern "C" {
S<uint32_t, void> f();
} // extern "C"
Which I believe to be unusable, because you can't create an object of type void
. However this is already what c_void
does, so why not. This does lead to the second issue
2. This conflicts with c_void
! So the following code
#[repr(C)]
pub struct S<T> {
x: std::marker::PhantomData<T>,
}
#[no_mangle]
pub extern "C" fn create() -> *mut S<std::ffi::c_void> {
loop {}
}
#[no_mangle]
pub extern "C" fn consume(_: *mut S<()>) {}
Produces
template<typename T>
struct S {};
extern "C" {
S<void> *create();
void consume(S<void>*);
} // extern "C"
And we can then write the incorrect code
void evil() {
auto x = create();
consume(x); // incorrect type !
}
But there are no other canonical sero-sized type in C++...
To conclude I don't think using ()
in cbindgen ever makes sense (except for the return type), and it should probably be an error to try to do so :upside_down_face:
I think there is a way to properly accept ()
, as well as other zero-sized types like PhantomData
inside the generated struct parameters. While C++ does not have a way to represent zero sized type, you can always omit the struct field when there is one. It can be done with partial specialization, like here:
template<typename T, typename C, typename R>
struct CGlueObjContainer {
T instance;
C context;
R ret_tmp;
};
template<typename T, typename C>
struct CGlueObjContainer<T, C, void> {
T instance;
C context;
};
Then replace any ()
with void, instead of nothing, and have it magically disappear in the struct rather than parameter.
Now, to do this fully you have 2^N possible specializations to do for N typename parameters used as a value type in the struct, which is less than ideal. It could be mitigated by excluding "type parameters that will never be void" or doing it inclusively instead. In the end, it could solve this issue alongside https://github.com/eqrion/cbindgen/issues/99.