Reputation: 47
I need to access an std::array<double, 9> x by two functions, namely funA and funB. funA only needs the first 6 entries of x and funB the last 3 entries. I am limited to C++14.
I know there are many possibilities to do so, however, I am trying to figure out whether my way is OK in your opinion. Performance has highest priority in my case, and since my code is running in an infinite loop copying is a no go.
What I did is:
// somewhere in the class definition: declare member variables of a class
void funA(const std::array<double, 6> &xA);
void funB(const std::array<double, 3> &xB);
std::array<double, 9> x;
std::array<double, 6> xA;
std::array<double, 3> xB;
// somewhere in the loop
std::move(x.begin(), x.begin() + xA.size(), xA.begin());
std::move(x.begin() + xA.size(), x.end(), xB.begin());
funA(xA);
funB(xB);
std::move(xA.begin(), xA.end(), x.begin());
std::move(xB.begin(), xB.end(), x.begin() + xA.size());
So, I though I simply move the corresponding pieces of x into xA and xB, I call funA and funB and finally I stitch x back together from xA and xB in order to keep going with x for the rest of my code. Note: xA and xB are passed as const reference.
Is that a valid way to go? Is there something that could go wrong? Is moving back and forth OK or a no go? Or is this procedure not that fast at all?
So far, in my few testings it seems to work, however, I don't feel safe yet. I didn't get too much in contact with std::move yet, so any comment or hint is very much appreciated.
Upvotes: 1
Views: 170
Reputation: 1059
This isn't the prettiest code since it uses reinterpret_cast.
It is probably safe because std::array
is a thin layer over a C-array.
Live code. It compiles with GCC 1.4 and CLang 19.1 with -std=c++14 -Wall -Wextra -pedantic-errors.
As the comments indicate, this is undefined behavior. I believe it is totally safe. Here is the definition of std::array.
template<class T,std::size_t N> struct array;
The size is a template parameter. It is used only in the compiler and not stored as a member. The two size commands return the same value, N. The cast tells the compiler to treat this array
as a shorter version of the existing array. What would be very BAD is casting to a greater number of elements. The code from the OP is only going shorter.
The reinterpret_cast
converts a pointer to the std::array<double, 9>
into a std::array<double, N>
where N is 6 or 3, as required. The address of the 6th element is used to get the trailing three elements.
#include <array>
#include <fmt/ranges.h>
using fmt::println;
using fmt::print;
void funA(const std::array<double, 6> &xA) {
println("{}", xA);
}
void funB(const std::array<double, 3> &xB) {
println("{}", xB);
}
auto main() -> int {
std::array<double, 9> x{1,2,3,4,5,6,7,8,9};
auto const* xA = reinterpret_cast<std::array<double, 6>*>(&x);
funA(*xA);
funB(*reinterpret_cast<std::array<double, 3>const *>(&x[6]));
}
Upvotes: -1
Reputation: 7077
I think using a pair of iterators was the idiomatic C++ way, at least until std::span
was introduced.
I've got a 'partial' answer using templates
#include <array>
#include <iostream>
template <typename T, int N>
void print_array(typename std::array<T, N>::iterator from,
typename std::array<T, N>::iterator to) {
std::cout << "{ ";
for (auto i = from; i != to; ++i) {
if (i != from) {
std::cout << ", ";
}
std::cout << *i;
}
std::cout << " }" << std::endl;
}
int main() {
std::array<double, 9> view9{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9};
print_array<double, 9>(std::begin(view9), std::end(view9));
print_array<double, 9>(std::begin(view9), std::begin(view9) + 6);
print_array<double, 9>(std::end(view9) - 3, std::end(view9));
}
I don't like having to explicitly give the template params to print_array
- see comments on this answer.
One option is to make it generic over all iterators
template <typename Iter>
void print_array(Iter from,
Iter to) {
std::cout << "{ ";
for (auto i = from; i != to; ++i) {
if (i != from) {
std::cout << ", ";
}
std::cout << *i;
}
std::cout << " }" << std::endl;
}
Note - the OP was concerned about performance. For std::array
using iterators adds no overhead compared to pointers (you can check by looking at the generated code)
(edit I copied the setup of this from @darkproduct answer using span
so you can compare the two approaches )
Upvotes: 1
Reputation: 1243
If you are using C++20 or higher you can use std::span
. Which is a non owning view into a container. Any edit to the view will result in a change of the original data in the container and will also be shown in any other overlapping view.
#include <iostream>
#include <array>
#include <span>
void print_span(const std::span<double>& arr) {
std::cout << "{ ";
for (size_t i = 0; i < arr.size(); ++i) {
std::cout << arr[i];
if (i < arr.size() - 1) {
std::cout << ", "; // Print a comma between elements
}
}
std::cout << " }" << std::endl;
}
int main() {
std::array<double, 9> arr{ 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9 };
std::span<double> first6(arr.data(), 6);
std::span<double> last3(arr.data() + 6, 3);
print_span(arr); // note that you can pass an std::array as well
print_span(first6);
print_span(last3);
}
To expand on this answer, if you cannot use C++20, here is a quick implementation of your span class that should suffice in most cases as a quick replacement. If you want to use with the above class save as my_span.h
, include that instead and change std::span
to my_span
. Note the first example above won't work with this class. Instead of passing an array to print_span
you always have to create a my_span
.
#include <cstddef>
#include <stdexcept>
template <typename T>
class my_span {
public:
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using size_type = std::size_t;
using iterator = T*;
using const_iterator = const T*;
my_span() : data_(nullptr), size_(0) {}
my_span(T* ptr, size_type count) : data_(ptr), size_(count) {}
my_span(T* first, T* last) : data_(first), size_(last - first) {}
// element access
reference operator[](size_type index) {
return data_[index];
}
const_reference operator[](size_type index) const {
return data_[index];
}
reference at(size_type index) {
if (index >= size_) {
throw std::out_of_range("Span index out of range");
}
return data_[index];
}
const_reference at(size_type index) const {
if (index >= size_) {
throw std::out_of_range("Span index out of range");
}
return data_[index];
}
reference front() { return data_[0]; }
const_reference front() const { return data_[0]; }
reference back() { return data_[size_ - 1]; }
const_reference back() const { return data_[size_ - 1]; }
pointer data() { return data_; }
const_pointer data() const { return data_; }
iterator begin() { return data_; }
const_iterator begin() const { return data_; }
const_iterator cbegin() const { return data_; }
iterator end() { return data_ + size_; }
const_iterator end() const { return data_ + size_; }
const_iterator cend() const { return data_ + size_; }
size_type size() const { return size_; }
bool empty() const { return size_ == 0; }
private:
T* data_;
size_type size_;
};
As another option, most C++ functions operate on iterator pairs. The first iterator pointing to the first element, the second iterator pointing one beyond the last element. In my experience that makes sense in most situations. Maybe yours is one of these most situations too?
Upvotes: 3
Reputation: 63142
Moving doubles
is a copy. You need to change the parameters of funA
and funB
to take a view of your array. The easiest would be with the C style double * ptr, size_t size
, but you can wrap that in a struct.
Microsoft's GSL library includes a span
type, very similar to std::span
, and is compatible with C++14.
e.g.
// somewhere in the class definition: declare member variables of a class
std::array<double, 9> x;
// somewhere in the loop
funA(gsl::span<double, 9>(x).first<6>());
funB(gsl::span<double, 9>(x).last<3>());
Upvotes: 1