In the C++ application that I am writing, at some point, I have to interface with a C API and call a function whose signature is
int foo(const double(*bar)[3], const int N)
My bar
is a std::vector<double>
whose size is a multiple of 3. This design is required by other parts of the application and cannot be changed. Therefore, to pass bar
from my source code to foo
, I arranged the following C-style solution:
auto bar = std::vector<double>(3 * N); // for some int N known only at runtime
// a lot of code that uses bar
// ...
// Call to C function "foo"
auto *bar_p = bar.data()
int result = foo((double(*)[3])(&bar_p), N)
which compiled but made clang-tidy to complain about using C-style arrays. I also tried with some static_cast
solutions, but the compiler did not accept them. Furthermore, clang-tidy also complained when I tried to use reinterpret_cast
.
Which is the supposed/canonical/best/"right" C++-way to pass bar
to foo
?
P.S.: C++17 or higher
Since you need to be able to provide a pointer to the first double[3]
in a contiguous memory area, you will need to actually create double[3]
s in that area. The simple solution is to use a std::vector<double[3]>
or even a std::unique_ptr<double[3]>
and suppress the clang-tidy warning. Provide a motivation, should you get negative code reviews:
// NOLINTNEXTLINE(modernize-avoid-c-arrays) C array used with C API
std::vector<double[3]> bar(N);
// ...
int result = foo(bar.data(), static_cast<int>(bar.size()));
or
// NOLINTNEXTLINE(modernize-avoid-c-arrays) C array used with C API
std::unique_ptr<double[3]> bar(new double[N][3]{});
int result = foo(bar.get(), N);
I am afraid there is no std portable solution to the problem. But it's at least possible to force compilation to fail if a supposed solution is not applicable. I am a proponent of always using std::array
instead of raw C arrays. When interfaced with C API, we are usually certain that data types have standard layouts. So, we only need to assert that std::array
can replace raw C arrays. So here's my first trail:
#include <span>
#include <array>
#include <type_traits>
typedef double raw_double_3[3];
using double_3 = std::array<std::remove_extent_t<raw_double_3> ,std::extent_v<raw_double_3>>;
auto cpp_foo(std::span<const double_3> val){
static_assert(std::is_standard_layout_v<double_3>);
static_assert(sizeof(double_3)==sizeof(raw_double_3));
auto const &ref = reinterpret_cast<const raw_double_3&>(front(val));
return foo(&ref, size(val));
};
This wrapper can handle std::vector<double_3>
as well as std::array<double_3, N>
or raw array of double_3
elements. As long as the assertions succeed(very [[likely]]), the compiled wrapper is UB-free. In the unlikely event that the assertions fail, we can look for other workarounds. In either case we should investigate provable preconditions for UB-free and declare static_assert
on them. Thus, the code either runs bug-free, or fails to compile.
Compiler/sanitizer warnings may still be present; because we are doing some unorthodox ad-hoc anyway. But it's readable and we have the safety proof (proper static_assert
declarations), which also serve as minimal self documentation too.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With