I have a container class like this:
class A {
public:
// assuming all the other operators used below
// have been defined.
// ...
A operator()(const A& a) const {
A r(a.size());
for (int i = 0;i < a.size();++i) r[i] = data[a[i]];
return r;
}
private:
std::vector<int> data;
};
so I can do things like this:
A a, b, c;
// ... initialize data here...
c = a(b); // I can index a by b
Now I want to make the indexed container a(b) assignable, e.g.
a(b) = c;
for example, if a is {1, 2, 3, 4} and b is {0,2}, c is {0,0}, the above line should give me a = {0,2,0,4}. because a indexed by {0,2} is {1,3} in a, and set them to c {0,0} will give me this.
how to do that?
You can't do it directly with the type A. You would need an intermediate type that maps or references back to the object. As a simple, non-optimal example:
#include <functional>
#include <vector>
class A
{
public:
class Ref
{
private:
friend class A;
std::vector<std::reference_wrapper<int>> refs;
Ref( A & obj, A & idx )
{
refs.reserve( idx.data.size() );
auto val_it = obj.data.begin();
for( auto i : idx.data ) {
ref.emplace_back( std::ref( data[i] ) );
}
}
public:
Ref & operator=( const A & obj )
{
auto obj_it = obj.data.begin();
for( auto ref : refs ) {
ref.get() = *obj_it++;
}
return this;
}
};
// ...
Ref operator()( const A & idx )
{
return Ref( *this, idx );
}
private:
std::vector<int> data;
};
And then things start getting fiddly, because you'll want to be able to convert between these reference-based views and the original type.
The easy way is to keep your const operator as you implemented (note that my one is non-const): i.e. it still returns type A, which makes sense. But you will probably want to be able to build a new A from a A::Ref, and so you'd want a constructor like:
A::A( const Ref & r )
{
data.reserve( r.refs.size() );
auto r_it = r.refs.begin();
for( auto ref : r.refs ) {
data.push_back( ref.get() );
}
}
Anyway, that's an easy concept to get you started, and something to play around with.
An array view is a view into a contiguous buffer of T:
template<class T>
struct array_view {
T* b=0;
T* e=0;
T* begin() const { return b; }
T* end() const { return e; }
std::size_t size() const { return end()-begin(); }
bool empty() const { return end()==begin(); }
T& operator[](std::size_t i)const { return begin()[i]; }
array_view( array_view const& ) = default;
array_view& operator=( array_view const& ) = default;
array_view() = default;
array_view( T* s, T* f ):b(s),e(f) {}
array_view( T* s, std::size_t n ):array_view(s, s+n) {}
};
template<class Src>
array_view< std::remove_reference_t<
decltype(*(std::declval<Src&>().data()))
> >
make_array_view( Src& src ) {
return {src.data(), src.size()};
}
(To make it better, do general "range" upgrades. const in the above case refers to "changing what range is viewed" not the contents -- if you want const contents, make an array_view<const T>. Another improvement would be to hvae constructors that do what make_array_view does, also supporting initializer_list, rvalues, and raw C arrays).
Given that, here is a permuted view of an array view of T.
First a permutation is a function from a bounded set of size_t to a different set of size_t.
struct permutation {
std::function< std::size_t(std::size_t) > mapping;
std::size_t count = 0;
std::size_t size() const { return count; }
permutation( std::function< std::size_t(std::size_t) > m, std::size_t c):
mapping(std::move(m)),
count(c)
{}
std::size_t operator()( std::size_t i )const {
return mapping(i);
}
};
This isn't the safest, because we don't check that the output range is reasonable.
A factory function:
template<class T>
permutation make_permutation_from( T src ) {
auto size = src.size();
return {
[src = std::move(src)]( std::size_t in ) {
return src[in];
},
size
};
}
// optimization
permutation make_permutation_from( permutation src ) {
return src;
}
and one to compose two permutations. The validity of the size field is not checked.
// if they don't align, we are screwed, but...
permutation chain_permutation( permutation first, permutation second ) {
auto first_size = first.size();
return {
[first=std::move(first), second=std::move(second)](std::size_t i){
return second(first(i));
},
first_size
};
}
This leads us to the permuted view, which is a view of an array_view that permutes indexes.
template<class T>
struct permuted_view {
array_view<T> source;
permutation permute;
std::size_t size() const { return permute.size(); }
T& operator[]( std::size_t i ) const {
return source[ permute(i) ];
}
template<class Src>
void assign_from( Src const& src ) {
if (src.size() != size()) exit(-1);
for (std::size_t i = 0; i < size(); ++i)
(*this)[i] = src[i];
}
void operator=( permuted_view const& src ) { assign_from(src); }
template<class U>
void operator=( U const& src ) { assign_from(src); }
template<class U,
std::enable_if_t< !std::is_integral<U>{}, int> =0
>
permuted_view<T> operator[]( U u )const {
return {
source,
chain_permutation( make_permutation_from(std::move(u)), permute )
};
}
};
Now a permuted_view<int> is a permutation on an array of int.
Note that permuted_view doesn't own anything. It just refers to someone else's storage. Ownership is something you'll have to work out for yourself. Maybe through smart pointers, or some other means.
Efficient libraries for this purpose probably have copy-on-write sparse arrays. To get this, it is a lot of work, or you should find a library like Eigen.
live example.
You'll want to add an iterator and begin/end to permuted_view. I'd make the iterator store a pointer to the view and an index, and have it use operator[] on the view when you dereference.
If you refactor array_view<T> into range_view<Iterator> with a specialization or subclass for T* iterators, you can then refactor range_view<Iterator> into range_helper<Iterator, Derived> with CRTP. Then reuse range_helper for permuted_view and for range_view, and range_view for array_view. But that is getting a bit off the reservation.
Various libraries, including Rangesv3 and boost and C++17 std and C++20 std::experimental and the like, have either written these types or make writing these types easier.
Related
Say I have a struct:
struct Boundary {
int top;
int left;
int bottom;
int right;
}
and a vector
std::vector<Boundary> boundaries;
What would be the most C++ style way to access the structs to get the sum of top, left, bottom and right separately?
I could write a loop like
for (auto boundary: boundaries) {
sum_top+=boundary.top;
sum_bottom+=boundary.bottom;
...
}
This seems like a lot of repetition. Of course I could do this instead:
std::vector<std::vector<int>> boundaries;
for (auto boundary: boundaries) {
for(size_t i=0; i<boundary.size();i++) {
sums.at(i)+=boundary.at(i)
}
}
But then I'd loose all the meaningful struct member names. Is there a way so that I can write a something like the following function:
sum_top=make_sum(boundaries,"top");
Reflection does not seem to be an option in C++. I am open to use C++ up to Version 14.
std::accumulate(boundaries.begin(), boundaries.end(), 0,
[](Boundary const & a, Boundary const & b) { return a.top + b.top); });
(IIRC the Boundary const &'s can be auto'd in C++17)
This doesn't make it generic for the particular element, which - indeed, due to the lack of reflection - isn't easy to generalize.
There are a few ways to ease your pain, though;
You could use a pointer-to-member, which is fine for your szenario but not very c-plusplus-y:
int Sum(vector<Boundary>const & v, int Boundary::*pMember)
{
return std::accumulate( /*...*/,
[&](Boundary const & a, Boundary const & b)
{
return a.*pMember + b.*pMember;
});
}
int topSum = Sum(boundaries, &Boundary::top);
(For pointer-to-member, see e.g. here: Pointer to class data member "::*")
You could also make this generic (any container, any member type), and you could also replace the pointer-to-member with a lambda (also allowing member functions)
You can achieve the desired effect with Boost Hana reflection:
#include <iostream>
#include <vector>
#include <boost/hana.hpp>
struct Boundary {
BOOST_HANA_DEFINE_STRUCT(Boundary,
(int, top),
(int, left),
(int, bottom),
(int, right)
);
};
template<class C, class Name>
int make_sum(C const& c, Name name) {
int sum = 0;
for(auto const& elem : c) {
auto& member = boost::hana::at_key(elem, name);
sum += member;
}
return sum;
}
int main() {
std::vector<Boundary> v{{0,0,1,1}, {1,1,2,2}};
std::cout << make_sum(v, BOOST_HANA_STRING("top")) << '\n';
std::cout << make_sum(v, BOOST_HANA_STRING("bottom")) << '\n';
}
See Introspecting user-defined types for more details.
I am probably a bit late to the party, but I wanted to add answer inspired by the one of #TobiasRibizel. Instead of adding much boilerplate code to your struct we add more boilerplate code once in the form of an iterator over (specified) members of a struct.
#include <iostream>
#include <string>
#include <map>
template<class C, typename T, T C::* ...members>
class struct_it {
public:
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = T*;
using reference = T&;
using iterator_category = std::bidirectional_iterator_tag;
constexpr struct_it (C &c) : _index{0}, _c(c)
{}
constexpr struct_it (size_t index, C &c) : _index{index}, _c(c)
{}
constexpr static struct_it make_end(C &c) {
return struct_it(sizeof...(members), c);
}
constexpr bool operator==(const struct_it& other) const {
return other._index == _index; // Does not check for other._c == _c, since that is not always possible. Maybe do &other._c == &_c?
}
constexpr bool operator!=(const struct_it& other) const {
return !(other == *this);
}
constexpr T& operator*() const {
return _c.*_members[_index];
}
constexpr T* operator->() const {
return &(_c.*_members[_index]);
}
constexpr struct_it& operator--() {
--_index;
return *this;
}
constexpr struct_it& operator--(int) {
auto copy = *this;
--_index;
return copy;
}
constexpr struct_it& operator++() {
++_index;
return *this;
}
constexpr struct_it& operator++(int) {
auto copy = *this;
++_index;
return copy;
}
private:
size_t _index;
C &_c;
std::array<T C::*, sizeof...(members)> _members = {members...}; // Make constexpr static on C++17
};
template<class C, typename T, T C::* ...members>
using cstruct_it = struct_it<const C, T, members...>;
struct boundary {
int top;
int bottom;
int left;
int right;
using iter = struct_it<boundary, int, &boundary::top, &boundary::bottom, &boundary::left, &boundary::right>;
using citer = cstruct_it<boundary, int, &boundary::top, &boundary::bottom, &boundary::left, &boundary::right>;
iter begin() {
return iter{*this};
}
iter end() {
return iter::make_end(*this);
}
citer cbegin() const {
return citer{*this};
}
citer cend() const {
return citer::make_end(*this);
}
};
int main() {
boundary b{1,2,3,4};
for(auto i: b) {
std::cout << i << ' '; // Prints 1 2 3 4
}
std::cout << '\n';
}
It works on C++14, on C++11 the constexpr functions are all const by default so they don't work, but just getting rid of the constexpr should do the trick. The nice thing is that you can choose just some members of your struct and iterate over them. If you have the same few members that you will always iterate over, you can just add a using. That is why I chose to make the pointer-to-members part of the template, even if it is actually not necessary, since I think that only the iterators over the same members should be of the same type.
One could also leave that be, replace the std::array by an std::vector and choose at runtime over which members to iterate.
Without going too much into the memory layout of C++ objects, I would propose replacing the members by 'reference-getters', which adds some boilerplate code to the struct, but except for replacing top by top() doesn't require any changes in the way you use the struct members.
struct Boundary {
std::array<int, 4> coordinates;
int& top() { return coordinates[0]; }
const int& top() const { return coordinates[0]; }
// ...
}
Boundary sum{};
for (auto b : boundaries) {
for (auto i = 0; i < 4; ++i) {
sum.coordinates[i] += b.coordinates[i];
}
}
I have the following code:
#include "stdafx.h"
#include <map>
#include <string>
#include <iostream>
class MyObject
{
public:
MyObject()
: m_Items{ { 1, "one" },{ 2, "two" },{ 3, "three" } }
{}
RETURNTYPE GetStringIterator() const
{
IMPLEMENTATION
}
private:
std::map<int, std::string> m_Items;
};
int main()
{
MyObject o;
for (auto& s : o.GetStringIterator())
{
std::cout << s;
}
}
What should RETURNTYPE and IMPLEMENTATION be in order to allow any client of MyObject (in this case the main() function), to iterate over the values of the m_Items map, without copying any data? It seems that this should be possible with c++11 range based for loops and iterators. but I have not been able to figure out how.
range-based iteration can be achieved like this:
class MyObject
{
public:
MyObject()
: m_Items{ { 1, "one" },{ 2, "two" },{ 3, "three" } }
{}
auto begin() { return m_Items.begin(); }
auto begin() const { return m_Items.begin(); }
auto end() { return m_Items.end(); }
auto end() const { return m_Items.end(); }
private:
std::map<int, std::string> m_Items;
};
Copying or not copying the value depends on how the code is written at the call site:
MyObject a;
for(auto [key,value] : a) {} // copies are made
for(auto & [key,value] : a) {} // no copy
for(auto const & [key,value] : a) {} // no copy
And you can disable the modification of map values by removing the non-const versions of begin and end :
class MyObject
{
public:
MyObject()
: m_Items{ { 1, "one" },{ 2, "two" },{ 3, "three" } }
{}
auto begin() const { return m_Items.begin(); }
auto end() const { return m_Items.end(); }
private:
std::map<int, std::string> m_Items;
};
Then, attempts at modifying the value in a range-for loop will lead to a compilation error:
MyObject a;
for(auto & [key,value] : a) {
//value.push_back('a'); // Not OK
}
for(auto & [key,value] : a) {
cout << value; // OK
}
Note that if the map is an implementation detail, the answer proposed by #Barry should be used, because it iterates only on the values of the map, not on the keys too.
You could use boost::adaptors::map_values, it works in C++11:
auto GetStringIterator() const
// NB: have the move the declaration of m_Items ahead of this function for this to work
-> decltype(m_Items | boost::adaptors::map_values)
{
return m_Items | boost::adaptors::map_values;
}
Or its range-v3 equivalent, view::values. Both can be used like values(m) instead of m | values, if you prefer it that way.
Either solution returns a view onto the values of the map. This is an object that doesn't own any of its underlying elements and is cheap to copy - that is, O(1). We're not coyping the map, or any of its underlying elements.
You would use this as if it were any other range:
for (std::string const& s : o.GetStringIterator()) {
// ...
}
This loop does not copy any strings. Each s refers directly into the corresponding string that the map is storing.
I'm going to first answer this in c++14.
Here is a minimal mapping iteratoroid:
template<class F, class It>
struct iterator_mapped {
decltype(auto) operator*() const {
return f(*it);
}
iterator_mapped( F f_in, It it_in ):
f(std::move(f_in)),
it(std::move(it_in))
{}
iterator_mapped( iterator_mapped const& ) = default;
iterator_mapped( iterator_mapped && ) = default;
iterator_mapped& operator=( iterator_mapped const& ) = default;
iterator_mapped& operator=( iterator_mapped && ) = default;
iterator_mapped& operator++() {
++it;
return *this;
}
iterator_mapped operator++(int) {
auto copy = *this;
++*this;
return copy;
}
friend bool operator==( iterator_mapped const& lhs, iterator_mapped const& rhs ) {
return lhs.it == rhs.it;
}
friend bool operator!=( iterator_mapped const& lhs, iterator_mapped const& rhs ) {
return !(lhs==rhs);
}
private:
F f;
It it;
};
it is not technically an iterator, but it qualifies for for(:) loops.
template<class It>
struct range_t {
It b, e;
It begin() const { return b; }
It end() const { return e; }
};
template<class It>
range_t<It> range( It b, It e ) {
return {std::move(b), std::move(e)};
}
the above is an absolutely minimal iterator range type that can be for(:) iterated.
template<class F, class R>
auto map_range( F&& f, R& r ) {
using std::begin; using std::end;
auto b = begin(r);
auto e = end(r);
using it = iterator_mapped<std::decay_t<F>, decltype(b)>;
return range( it( f, b ), it( f, e ) );
}
note that R& not R&&; taking an rvalue for r here is dangerous.
auto GetStringIterator() const
{
return map_range( [](auto&& pair)->decltype(auto){
return pair.second;
}, m_Items );
}
and done.
Converting this to c++11 is a pain. You have to toss around std::functions in place of lambdas (or write function objects that do the task instead of a lambda), replace decltype(auto) with auto and trailing return types, give the exact type of auto&& arguments to lambdas, etc. You end up with about 25%-50% more code, most of it obscure type chasing.
This is basically what boost::adaptors::map_values does, but this is hand-rolled so you can understand how it works and don't have a boost dependency.
Is there an std container for C-style arrays with variable size?
For example, I have the following code
int size = 5; // This is not a constant in general
int *my_array = SpecialAllocationFunction(size);
I want to be able to access this array with a C++, std-style container. Something that has iterators and functions like: size, begin, end, ...
I know that I can use std::array if my_array has a constant size. I also can write one myself, but I feel there bound to be something ready-made.
Using a custom allocator, it is possible to build a vector with a size known only at run time (so no std::array possible), wrapping an existing array. It is even possible to keep pre-existing values by overriding the special construct method(*).
Here is a possible implementation:
/**
* a pseudo allocator which receives in constructor an existing array
* of a known size, and will return it provided the required size
* is less than the declared one. If keep is true in contructor,
* nothing is done at object construction time: original values are
* preserved
* at deallocation time, nothing will happen
*/
template <class T>
class SpecialAllocator {
T * addr;
size_t sz;
bool keep;
public:
typedef T value_type;
SpecialAllocator(T * addr, size_t sz, bool keep):
addr(addr), sz(sz), keep(keep) {}
size_t max_size() {
return sz;
}
T* allocate(size_t n, const void* hint=0) {
if (n > sz) throw std::bad_alloc(); // throws a bad_alloc...
return addr;
}
void deallocate(T* p, size_t n) {}
template <class U, class... Args>
void construct(U* p, Args&&... args) {
if (! keep) {
::new((void *)p) U(std::forward<Args>(args)...);
}
}
template <class U>
void destroy(U* p) {
if (! keep) {
p->~U(); // do not destroy what we have not constructed...
}
}
};
It can then be used that way:
int size = 5; // This is not a constant in general
int *my_array = SpecialAllocationFunction(size);
SpecialAllocator<int> alloc(my_array, size);
std::vector<int, SpecialAllocator<int> > vec(size, alloc);
From that point, vec will be a true std::vector wrapping my_array.
Here is a simple code as demo:
int main(){
int arr[5] = { 5, 4, 3, 2, 1 };
SpecialAllocator<int> alloc(arr, 5, true); // original values will be preserved
std::vector<int, SpecialAllocator<int> > vec(5, alloc);
for(auto it= vec.begin(); it != vec.end(); it++) {
std::cout << *it << " ";
}
std::cout << std::endl;
try {
vec.push_back(8);
}
catch (std::bad_alloc& a) {
std::cout << "allocation error" << std::endl;
}
return 0;
}
It will successfully output:
5 4 3 2 1
allocation error
(*) BEWARE: Construction/destruction may be involved in different places: push_back, emplace_back, etc. Really think twice about your real use case before using no-op construct and destroy methods.
As #NathanOliver and #utnapistim said in the comments, gsl::span works. Since I don't want to include this library I ended up writing a "trivial wrapper" myself. Included below for others looking for an answer (and my future self)
template<class T>
class span {
public:
inline span() : _data(0), _size(0) {}
inline span(T* d, size_t s) : _data(d), _size(s) {}
inline T& operator[](size_t index) { return _data[index]; }
inline const T& operator[](size_t index) const { return _data[index];}
inline size_t size() const { return _size; };
inline T* begin() { return _data; }
inline const T* begin() const { return _data; }
inline T* end() { return _data+_size; }
inline const T* end() const { return _data+_size; }
protected:
T* _data;
size_t _size;
};
I would like to have a class member variable to be able to switch in between items in a map so that when it is modified, the content of the map is also modified.
Is there any way other than to use a pointer to the content of the map ? Old code only needed only variable, now the new one needs to switch. If I change the variable type, then all functions using this member variable need to be changed. Not complicated, but I would find it ugly to have * in front of it everywhere...
A reference variable cannot be rebound, so how can I achieve this ?
class A
{
std::map<std::string,std::vector<int>> mMyMap;
std::vector<int>& mCurrentVector;
std::vector<int>* mCurrentVectorPointer;
std::vector<int> mDefaultVector;
void setCurrentVector(int iKey);
void addToCurrentVector(int iValue);
}
A::A():
mDefaultVector(std::vector<int>())
mCurrentVector(mDefaultVector)
{
mMyMap["key1"] = std::vector<int>(1,1);
mMyMap["key2"] = std::vector<int>(1,2);
mCurrentVectorPointer = &mMyMap[0];
}
A::setCurrentVector(std::string iKey)
{
if(mMyMap.find(iKey) != mMyMap.end())
{
mCurrentVector = mMyMap[iKey]; //can't change a reference...
mCurrentVectorPointer = &mMyMap[iKey]; //could use pointer, but
}
}
A::addToCurrentVector(int iValue)
{
mCurrentVector.push_back(iValue);
//or
(*mCurrentVectorPointer).push_back(iValue);
//
mCurrentVectorPointer->push_back(iValue);
}
void main()
{
A wClassA();
wClassA.setCurrentVector("key2");
wClassA.addToCurrentVector(3);
wClassA.setCurrentVector("key1");
wClassA.addToCurrentVector(4);
}
mMyMap["key1"] now contains 1,4
mMyMap["key2"] now contains 2,3
You can't reseat a reference once it has been assigned which means you are left with using your other option, a pointer.
As I understand it, you're refactoring some existing code which only used a single vector, whereas now you need a map of vectors.
You're trying to achieve this with minimal modifications, and keeping the interface to the vector the same.
An option would be to use a local reference, assigned from your pointer.
class A
{
using Vector = std::vector<int>;
public:
A()
{
map_["key1"] = std::vector<int>(1,1);
map_["key2"] = std::vector<int>(1,2);
curr_vec_ = &map_["key1"];
}
void setCurrentVector(const std::string& key)
{
if(map_.find(key) != map_.end())
{
curr_vec_ = &map_[key];
}
}
void addToCurrentVector(int val)
{
assert(curr_vec_);
Vector& curr_vec = *curr_vec_; // local reference
curr_vec.push_back(val);
curr_vec[0] = 2;
// etc
}
private:
std::map<std::string, Vector> map_;
Vector* curr_vec_ = nullptr;
}
You may write some wrapper:
#define Return(X) noexcept(noexcept(X)) -> decltype(X) { return X; }
template <typename U>
class MyVectorRef
{
private:
std::vector<U>* vec = nullptr;
public:
explicit MyVectorRef(std::vector<U>& v) : vec(&v) {}
void reset(std::vector<U>& v) {vec = &v;}
// vector interface
auto at(std::size_t i) const Return(vec->at(i))
auto at(std::size_t i) Return(vec->at(i))
auto operator [](std::size_t i) const Return(vec->operator[](i))
auto operator [](std::size_t i) Return(vec->operator[](i))
template <typename ... Ts> auto assign(Ts&&... ts) Return(vec->assign(std::forward<Ts>(ts)...))
auto assign( std::initializer_list<U> ilist ) Return(vec->assign(ilist))
template <typename T> auto push_back(T&& t) const Return(vec->push_back(std::forward<T>(t)))
template <typename T> auto emplace_back(T&& t) const Return(vec->emplace_back(std::forward<T>(t)))
auto begin() const Return(vec->begin())
auto begin() Return(vec->begin())
auto end() const Return(vec->end())
auto end() Return(vec->end())
auto cbegin() const Return(vec->cbegin())
auto cend() const Return(vec->cend())
// ...
};
and then, use it:
class A
{
public:
A() : mCurrentVector(mDefaultVector) {
mMyMap["key1"] = std::vector<int>(1,1);
mMyMap["key2"] = std::vector<int>(1,2);
}
std::map<std::string, std::vector<int>> mMyMap;
std::vector<int> mDefaultVector;
MyVectorRef<int> mCurrentVector;
void setCurrentVector(std::string iKey)
{
auto it = mMyMap.find(iKey);
if (it != mMyMap.end())
{
mCurrentVector.reset(it->second);
}
}
void addToCurrentVector(int iValue)
{
mCurrentVector.push_back(iValue);
}
};
But I think it would be simpler to just create a getter in A and use directly a pointer:
class A
{
public:
A() : mCurrentVector(&mDefaultVector) {
mMyMap["key1"] = std::vector<int>(1,1);
mMyMap["key2"] = std::vector<int>(1,2);
}
std::map<std::string, std::vector<int>> mMyMap;
std::vector<int> mDefaultVector;
std::vector<int>* mCurrentVector;
std::vector<int>& GeCurrentVector() { return *mCurrentVector; }
void setCurrentVector(std::string iKey)
{
auto it = mMyMap.find(iKey);
if (it != mMyMap.end())
{
mCurrentVector = &it->second;
}
}
void addToCurrentVector(int iValue)
{
GeCurrentVector().push_back(iValue);
}
};
For polymorphism, the usual approach is to use std::vector<base*>. However, I have to provide the addresses myself, that is to manage the memory myself whether I use std::unique_ptr<> or raw pointers.
I would like to have a polymorphic_storage<base> type that accepts any type that inherits from base. I also want the types to be stored in contiguous memory for faster traversal and for cache related concerns.
However, there's a pretty big issue: In the absence of type information at the storage level, the correct move/copy operations must be called on resize.
Feature request:
Any type that inherits from the base class can be added to the storage; no fixed inheritance hierarchies.
The inheriting types must be correctly aligned inside of the storage type.
The correct move and copy operations must be called since I am not dealing with POD types.
What mechanism can I use to achieve this?
While I provide an answer, I would welcome anyone to post their solution.
Now with alignment support.
Demo: http://coliru.stacked-crooked.com/a/c304d2b6a475d70c
This answer focuses on solving the three features requested in the question.
No static memory is used because it will lead to code breaking changes if a new type is added to the inheritance hierarchy and that new type exceeds the static limit.
All types inside the storage are properly aligned.
The correct move/copy constructors are called when reallocation occurs.
It compiles with fstrict-aliasing, so don't be too afraid of that reinterpret_cast<>() usage.
The handle_base type has a void* data member called src_, which points to some value. It has two member functions that act on src_.
void transfer( void* dst, std::size_t& out_size )
Uses placement-new to either move or copy construct the value pointed to by src_ at dst, then sets src_ to dst. It also adds the size, in bytes, taken by the type to the out_size reference argument; this is useful for properly aligning types.
void* src()
Returns the pointer src_.
handle_base.h
namespace gut
{
template<class T> class handle;
class handle_base
{
public:
virtual ~handle_base() = default;
handle_base() = default;
handle_base( handle_base&& ) = default;
handle_base( handle_base const& ) = default;
handle_base& operator=( handle_base&& ) = default;
handle_base& operator=( handle_base const& ) = default;
void* src() const noexcept
{
return src_;
}
virtual void transfer( void* dst, std::size_t& out_size ) = 0;
virtual void destroy() = 0;
protected:
handle_base( void* src ) noexcept
: src_{ src }
{}
void* src_;
};
}
Next, I create the handle<T> type which inherits from handle_base in order to provide the correct move/copy operations. Type information is available at this level; this enables everything from proper alignment to proper move/copy operations.
void transfer( void* dst, std::size_t& out_size )
The function will take care of selecting whether to use the move or copy constructor. The move constructor will always be chosen if it is available. It calculates any required padding for alignment, transfers the value at src_ to dst + padding and increments the out_size reference argument by its size and padding.
handle.h
namespace gut
{
template<class T>
static std::size_t calculate_padding( void* p ) noexcept
{
std::size_t r{ reinterpret_cast<std::uintptr_t>( p ) % alignof( T ) };
return r == 0 ? 0 : alignof( T ) - r;
}
template <class T>
class handle final : public handle_base
{
public:
using byte = unsigned char;
static_assert( sizeof( void* ) == sizeof( T* ),
"incompatible pointer sizes" );
static constexpr std::integral_constant
<
bool, std::is_move_constructible<T>::value
> is_moveable{};
handle( T* src ) noexcept
: handle_base( src )
{}
handle( handle&& ) = default;
handle( handle const& ) = default;
handle& operator=( handle&& ) = default;
handle& operator=( handle const& ) = default;
void transfer( std::true_type, void* dst )
noexcept( std::is_nothrow_move_assignable<T>::value )
{
src_ = ::new ( dst ) T{ std::move( *reinterpret_cast<T*>( src_ ) ) };
}
void transfer( std::false_type, void* dst )
noexcept( std::is_nothrow_copy_assignable<T>::value )
{
src_ = ::new ( dst ) T{ *reinterpret_cast<T*>( src_ ) };
}
virtual void transfer( void* dst, std::size_t& out_size )
noexcept( noexcept(
std::declval<handle>().transfer( is_moveable, dst ) ) ) override
{
std::size_t padding{ gut::calculate_padding<T>( dst ) };
transfer( is_moveable, reinterpret_cast<byte*>( dst ) + padding );
out_size += sizeof( T ) + padding;
}
virtual void destroy()
noexcept( std::is_nothrow_destructible<T>::value )
{
reinterpret_cast<T*>( src_ )->~T();
src_ = nullptr;
}
};
}
Since I know for a fact that sizeof( handle_base ) == sizeof( handle<T> ) for any T, I create a polymorphic_handle type as an extra indirection for ease of use. This type can hold any handle<T> and overloads operator->() so that it can act as a generic handle to any handle.
polymorphic_handle.h
namespace gut
{
class polymorphic_handle
{
public:
using value_type = gut::handle_base;
using pointer = value_type*;
using const_pointer = value_type const*;
template<class T>
polymorphic_handle( gut::handle<T> h ) noexcept
{
::new ( &h_ ) gut::handle<T>{ h };
}
pointer operator->()
{
return reinterpret_cast<pointer>( &h_ );
}
const_pointer operator->() const
{
return reinterpret_cast<const_pointer>( &h_ );
}
private:
std::aligned_storage_t<sizeof( value_type ), alignof( value_type )> h_;
};
}
Now that all the building blocks are present, the polymorphic_storage<T> type can be defined. It simply stores a std::vector<gut::polymorphic_handle>, a buffer and size information.
The storage type ensures that only classes that derive from its template argument type can be added. It can only be created with an initial instance or some initial capacity (in bytes).
template<class D> void ensure_capacity()
This function does almost all of the work. It ensures that there is enough capacity for the type specified as a template argument and transfers all data to a new buffer on reallocation. It also updates the size_ member function to the next construction location.
void emplace_back( D&& value )
This will emplace value into the polymorphic_storage<B> and create a handle to the newly emplaced value.
namespace gut
{
template<class B>
class polymorphic_storage
{
public:
using byte = unsigned char;
using size_type = std::size_t;
~polymorphic_storage() noexcept
{
for ( auto& h : handles_ )
{
h->destroy();
}
std::free( data_ );
}
explicit polymorphic_storage( size_type const initial_capacity )
{
byte* new_data
{
reinterpret_cast<byte*>( std::malloc( initial_capacity ) )
};
if ( new_data )
{
data_ = new_data;
size_ = 0;
capacity_ = initial_capacity;
}
else
{
throw std::bad_alloc{};
}
}
template
<
class D,
std::enable_if_t<std::is_base_of<B, std::decay_t<D>>::value, int> = 0
>
explicit polymorphic_storage( D&& value )
: data_{ nullptr }
, size_{ 0 }
, capacity_{ 0 }
{
using der_t = std::decay_t<D>;
byte* new_data{ reinterpret_cast<byte*>(
std::malloc( sizeof( der_t ) + alignof( der_t ) ) ) };
if ( new_data )
{
data_ = new_data;
size_ = sizeof( der_t );
capacity_ = sizeof( der_t ) + alignof( der_t );
handles_.emplace_back( gut::handle<der_t>
{
::new ( data_ ) der_t{ std::forward<D>( value ) }
} );
}
else
{
throw std::bad_alloc{};
}
}
template
<
class D,
std::enable_if_t<std::is_base_of<B, std::decay_t<D>>::value, int> = 0
>
void emplace_back( D&& value )
{
using der_t = std::decay_t<D>;
ensure_capacity<der_t>();
der_t* p{ ::new ( data_ + size_ ) der_t{ std::forward<D>( value ) } };
size_ += sizeof( der_t );
handles_.emplace_back( gut::handle<der_t>{ p } );
}
template
<
class D,
std::enable_if_t<std::is_base_of<B, std::decay_t<D>>::value, int> = 0
>
void ensure_capacity()
{
using der_t = std::decay_t<D>;
auto padding = gut::calculate_padding<der_t>( data_ + size_ );
if ( capacity_ - size_ < sizeof( der_t ) + padding )
{
auto new_capacity =
( sizeof( der_t ) + alignof( der_t ) + capacity_ ) * 2;
auto new_data = reinterpret_cast<byte*>(
std::malloc( new_capacity ) );
if ( new_data )
{
size_ = 0;
capacity_ = new_capacity;
for ( auto& h : handles_ )
{
h->transfer( new_data + size_, size_ );
}
std::free( data_ );
data_ = new_data;
}
else
{
throw std::bad_alloc{};
}
}
else
{
size_ += padding;
}
}
public:
std::vector<gut::polymorphic_handle> handles_;
byte* data_;
size_type size_;
size_type capacity_;
};
}
Here is an example of the storage in use. Note that types der0, der1 and der2 inherit from base and have different sizes and alignments.
Demo: http://coliru.stacked-crooked.com/a/c304d2b6a475d70c
#include <iostream>
#include <string>
struct base
{
virtual ~base() = default;
virtual void print() const = 0;
};
struct der0 : public base
{
der0( int&& i ) noexcept : i_{ i } {}
void print() const override { std::cout << "der0_" << i_ << '\n'; }
int i_;
};
struct der1 : public base
{
der1( std::string const& s ) noexcept : s_{ s } {}
void print() const override { std::cout << "der1_" << s_ << '\n'; }
std::string s_;
};
struct der2 : public base
{
der2( std::string&& s ) noexcept : s_{ std::move( s ) } {}
void print() const override { std::cout << "der2_" << s_ << '\n'; }
std::string s_;
double d[ 22 ];
};
int main()
{
gut::polymorphic_storage<base> ps{ 32 };
ps.emplace_back( der1{ "aa" } );
ps.emplace_back( der2{ "bb" } );
ps.emplace_back( der1{ "cc" } );
ps.emplace_back( der2{ "ee" } );
ps.emplace_back( der0{ 13 } );
ps.emplace_back( der2{ "ff" } );
for ( auto handle : ps.handles_ )
reinterpret_cast<base*>( handle->src() )->print();
}
This approach tried to avoid creating virtual objects in a buffer. Instead, it creates manual vtables, and extends them.
The first thing we start with is a value vtable that lets us deal with a value virtually:
struct value_vtable {
void(* copy_ctor)(void* dest, void const* src) = nullptr;
void(* move_ctor)(void* dest, void* src) = nullptr;
void(* dtor)(void* delete_this) = nullptr;
};
Creating one of these for a type T looks like this:
template<class T>
value_vtable make_value_vtable() {
return {
[](void* dest, void const* src) { // copy
new(dest) T( *(T const*)src );
},
[](void* dest, void * src) { // move
new(dest) T( std::move(*(T*)src) );
},
[](void* delete_this) { // dtor
((T*)delete_this)->~T();
},
};
}
We can store these vtables inline, or we can create static storage for them:
template<class T>
value_vtable const* get_value_vtable() {
static auto const table = make_value_vtable<T>();
return &table;
}
At this point we haven't stored anything.
Here is a value_storage. It can store any value (can be copied and moved and destroyed):
template<std::size_t S, std::size_t A>
struct value_storage {
value_vtable const* vtable;
std::aligned_storage_t<S, A> data;
template<class T,
std::enable_if_t<!std::is_same< std::decay_t<T>, value_storage >{}, int> =0,
std::enable_if_t< ( sizeof(T)<=S && alignof(T)<=A ), int > = 0
>
value_storage( T&& tin ) {
new ((void*)&data) std::decay_t<T>( std::forward<T>(tin) );
vtable = get_value_vtable<std::decay_t<T>>();
}
// to permit overriding the vtable:
protected:
template<class T>
value_storage( value_vtable const* vt, T&& t ):
value_storage( std::forward<T>(t) )
{
vtable = vt;
}
public:
void move_from( value_storage&& rhs ) {
clear();
if (!rhs.vtable) return;
rhs.vtable->move_ctor( &data, &rhs.data );
vtable = rhs.vtable;
}
void copy_from( value_storage const& rhs ) {
clear();
if (!rhs.vtable) return;
rhs.vtable->copy_ctor( &data, &rhs.data );
vtable = rhs.vtable;
}
value_storage( value_storage const& rhs ) {
copy_from(rhs);
}
value_storage( value_storage && rhs ) {
move_from(std::move(rhs));
}
value_storage& operator=( value_storage const& rhs ) {
copy_from(rhs);
return *this;
}
value_storage& operator=( value_storage && rhs ) {
move_from(std::move(rhs));
return *this;
}
template<class T>
T* get() { return (T*)&data; }
template<class T>
T const* get() const { return (T*)&data; }
explicit operator bool() const { return vtable; }
void clear() {
if (!vtable) return;
vtable->dtor( &data );
vtable = nullptr;
}
value_storage() = default;
~value_storage() { clear(); }
};
This type stores something that acts like a value, maybe, up to size S and align A. It does not store what type it stores, that is someone else's job. It does store how to copy, move and destroy anything it stores, but it doesn't know what the thing is it is storing.
It assumes objects constructed in a block are constructed at the front of it. You can add a void* ptr field if you do not want to make that assumption.
Now we can augment this value_storage with operations.
In particular, we want cast-to-base.
template<class Base>
struct based_value_vtable:value_vtable {
Base*(* to_base)(void* data) = nullptr;
};
template<class Base, class T>
based_value_vtable<Base> make_based_value_vtable() {
based_value_vtable<Base> r;
(value_vtable&)(r) = make_value_vtable<T>();
r.to_base = [](void* data)->Base* {
return (T*)data;
};
return r;
}
template<class Base, class T>
based_value_vtable<Base> const* get_based_value_vtable() {
static const auto vtable = make_based_value_vtable<Base, T>();
return &vtable;
}
Now we have extended the value_vtable to include a family of "vtable with base".
template<class Base, std::size_t S, std::size_t A>
struct based_value:value_storage<S, A> {
template<class T,
std::enable_if_t< !std::is_same< std::decay_t<T>, based_value >{}, int> = 0
>
based_value( T&& tin ):
value_storage<S, A>(
get_based_value_vtable<Base, std::decay_t<T> >(),
std::forward<T>(tin)
)
{}
template<class T>
based_value(
based_value_vtable<Base> const* vt,
T&& tin
) : value_storage<S, A>( vt, std::forward<T>(tin) )
{}
based_value() = default;
based_value( based_value const& ) = default;
based_value( based_value && ) = default;
based_value& operator=( based_value const& ) = default;
based_value& operator=( based_value && ) = default;
based_value_vtable<Base> const* get_vt() const {
return static_cast< based_value_vtable<Base>* >(this->vtable);
}
Base* get() {
if (!*this) return nullptr;
return get_vt()->to_base( &this->data );
}
Base const* get() const {
if (!*this) return nullptr;
return get_vt()->to_base( (void*)&this->data );
}
};
These are regular value types that store locally, are polymorphic, and can be anything descended from Base that match the size and alignment requirements.
Simply store a vector of these. And that solves your problem. These objects satisfy the axioms of value types that std::vector expects.
live example. Code not heavily tested (you can see the very small test), it probably still contains some typos. But the design is solid, I have done this before.
Augmenting with operator* and operator-> is an excercise left to the reader. If you want extreme performance at the cost of some size, you can store the vtable function pointers inline in the class instead of in shared memory.
If you find yourself doing this more than once or adding new capabilities to your based_value, a better extension than the bespoke based_value trick would be to automate the vtable extension procedure. I'd use something like type erasure with std::any, simply replacing the any with the value_storage<Size, Align> for guaranteed automatic storage, adding in better const support, and integrating the two vtables into one (like in based_value above).
In the end, we'd get:
template<class T>
auto to_base = [](auto&& self)->copy_const< decltype(self), T& > {
return decltype(self)(self);
};
template<class Base, std::size_t S, std::size_t A>
using based_value = super_value_storage< S, A, decltype(to_base<Base>) >;
using my_type = based_value< Some_Base, 100, 32 >;
my_type bob = // some expression
if (bob)
return (bob->*to_base<Some_Base>)()
else
return nullptr;
or somesuch.
All of the C-style casts can be replaced with a combination of static and const casts, but I got lazy. I don't believe I ever do anything requiring a reinterpret cast.
But really, once you have this kind of magic value-based polymorphism, why bother with a base at all? Simply erase all of the operations you want on the value, and accept anything that supports the erased operations.
With careful use of ADL in your any_methods, you can now concept-map distinct objects over to your set of concepts that must be supported. If you know how to chicken a vector of dogs, you can directly store a vector of dogs in a super_value_storage< Size, Align, ..., decltype(do_the_chicken) >.
That might be going too far, however.