Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

From std::vector to pointer-to-array (most C++-style solution)

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

like image 444
JtTest Avatar asked Sep 21 '25 04:09

JtTest


2 Answers

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);
like image 141
Ted Lyngmo Avatar answered Sep 22 '25 17:09

Ted Lyngmo


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.

like image 29
Red.Wave Avatar answered Sep 22 '25 17:09

Red.Wave