struct FooSlow {
std::vector<T*> getSubset(int i) const { return { _v.begin() + i, _v.end() }; }
std::vector<std::unique_ptr<T>> _v;
};
struct FooFast {
carray_view<T*> getSubset(int i) const { return { _v.begin() + 1, _v.end() }; }
std::vector<T*> _v;
~FooFast() { while(!_v.empty()) { delete v.back(); v.pop_back(); } }
};
struct FooFast {
carray_view<T*> getSubset(int i) const { return { _v.begin() + 1, _v.end() }; }
std::ptr_vector<T*> _v;
};
template <typename T>
T** unique_ptr_cast<T>(unique_ptr<T>* ptr) { return reinterpret_cast<T**>(ptr); }
struct FooFast {
carray_view<T*> getSubset(int i) const { return { unique_ptr_cast(_v.data() + i), unique_ptr_cast(_v.data() + v.size()) }; }
std::vector<std::unique_ptr<T*>> _v;
};
Il giorno 15/gen/2015, alle ore 16:46, Nicola Gigante <nicola....@gmail.com> ha scritto:That said, if in general reinterpret_cast from unique_ptr<T> to T* was not undefined behavior,it would be great. (Or am I missing something here?)
Enter code here...
Enter code here...
That said, if in general reinterpret_cast from unique_ptr<T> to T* was not undefined behavior,it would be great. (Or am I missing something here?)
extern "C" graphics_api_render(float* xyzw_points, size_t count);
struct Vec4 {
float x, y, z, w;
};
vec4 v[256];
//Fill in the vectors
//Pass to graphics API
graphics_api_render(reinterpret_cast<float*>(v), 256);
//Specialization for default unique_ptr
template <typename T>
unique_ptr<T,std::default_delete<T>) {
alias T*; //Says that we can alias_cast<T**>(this);
};
struct Vec4 {
float x, y, z, w;
alias float; //Says that we can alias_cast<float*>(this);
};
//Concepts: Fails to compile unless T can alias V* via alias keyword
template <typename T, typename V>
T alias_cast(V* obj) { return reinterpret_cast<T>(obj); }
If you return a range that “adapts” the vector<unique_ptr<T>> by calling .get() on the instances, you’re done.I used Boost.Range but I think that with the Niebler's library it would be as simple as:auto getSubset(int i) const { return _v | view::slice(i, _v.size()) | view::transform(&unique_ptr<T>::get); }; }This also nicely decouples your clients from the fact that you’re using a vector.So I think this problem will be solved when we’ll have ranges.
struct Foo {
vector<T*> inputs; //Owned by someone else
vector<std::vector<T*>> stages; //Owned by us
array_view<T*> getInputs() { return inputs; }
array_view<T*> getOutputs() {
return stages.empty() ? getInputs() : stages.back()
}
~Foo() { for(auto& s: stages) for(auto& t: s) delete s; }
};
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
Adapting ranges don't actually solve it for me, I'll show more of my use case:struct Foo {
vector<T*> inputs; //Owned by someone else
vector<std::vector<T*>> stages; //Owned by us
array_view<T*> getInputs() { return inputs; }
array_view<T*> getOutputs() {
return stages.empty() ? getInputs() : stages.back()
}
~Foo() { for(auto& s: stages) for(auto& t: s) delete s; }
};In this situation I'm really forced to manage the memory myself because std::unique_ptr<T> and T* are different types. If we could portably alias unique_ptr<T>* to T**, then I could take advantage of the automatic memory management.
Not ideal, but you could have an additional member:
vector<T*> current;
And have it initialized with 'inputs' and updated with the current view of the data whenever a stage completes... If you don't need to go back to the inputs after the first stage completes, you could reuse the 'inputs' member.
Regarding the aliasing of 'unique_ptr<T>*' with 'T**' (or even 'T *const *'), the problem is that this makes sense only for specializations of 'std::unique_ptr<T,D>' with stateless deleters, and would probably be confusing (why is it sometimes "convertible", sometimes not) or dangerous (reinterpreting the deleter as a 'T*').
On Friday, January 16, 2015 at 12:46:57 PM UTC-5, Nicola Gigante wrote:
I’m sorry, I don’t get it.Why can’t you return a view of inputs if stages is empty and return a view of stages.back() otherwise?
We have unique_ptr<T[]> and vector<unique_ptr<T>> but there is one big use case where both are inadequate.
struct FooSlow {
std::vector<T*> getSubset(int i) const { return { _v.begin() + i, _v.end() }; }
std::vector<std::unique_ptr<T>> _v;
};
struct FooFast {
carray_view<T*> getSubset(int i) const { return { _v.begin() + 1, _v.end() }; }
std::vector<T*> _v;
~FooFast() { while(!_v.empty()) { delete v.back(); v.pop_back(); } }
};
Here we have a linear array of pointers in our Foo object and want to return a view to a subsection of the array. With vector<unique_ptr<T>>, we are forced to create a copy because of the type system.With vector<T*>, we can directly return a view referencing a subset but now we have to take care of copy(), copy=, move(), move=, and the destructor.
Another alternative would be to have some guarantee in the standard that reinterpret_cast<T*>(std::unique_ptr<T>()) is valid.
Given that you said this is a big use case already, how are you dealing with copying FooSlow objects?
If you really want to do something like this, why can't you just use vector<shared_ptr<T>>? You at least get the pointer syntax with any range you return.
Another alternative would be to have some guarantee in the standard that reinterpret_cast<T*>(std::unique_ptr<T>()) is valid.Seems extremely unlikely C++ will move in this direction.
I don't see why this case is different than any other case where you have a vector<A> and want to return a range<B>. I don't see that the unique_ptr<T> -> T* case is all that special (or even common).
Another alternative would be to have some guarantee in the standard that reinterpret_cast<T*>(std::unique_ptr<T>()) is valid.Seems extremely unlikely C++ will move in this direction.Thats not surprising, such a feature seems low level, dangerous, and only useful in some narrow cases. Still it would be nice to have.
Even though its ugly, it would allow me in this case to design a nice interface with the ugly cast buried in the private implementation details.
The general problem is as you said vector<A> -> range<B>. Actually my case its even worse because I'm trying to do construct a range<B> sometimes from vector<A> and sometimes from vector<B>, so the hypothetical range type needs to keep track of how it was constructed and do the adaptation as needed.This is generally not possible unless your range object contains a lot of additional logic and references for transparently adapting A to B. This additional logic for doing the adaptation has some performance cost as well as adding complexity to the code. In terms of interface design, returning an array_view<T> is as self-explanatory as it gets. Returning some weird templated range object not as much.My situation is the case is when A is bitwise compatible with B and has the same semantics. In this situation the range can just reinterpret_cast A to B and be iterated over by the called with 0 overhead.For my particular example unique_ptr (with default deleter) is just a raw pointer with some extra type system magic to tell the C++ compiler to generate code to manage the memory for you.
My situation is the case is when A is bitwise compatible with B and has the same semantics. In this situation the range can just reinterpret_cast A to B and be iterated over by the called with 0 overhead.
Would it? This provides a hole in encapsulation as big as a truck.Do you really want to allow people to legally write
*reinterpret_cast<T**>(p) = nullptr;instead of p.release()? Or worse,*reinterpret_cast<T**>(p) = q;thus bypassing all of unique_ptr's invariants.
Even though its ugly, it would allow me in this case to design a nice interface with the ugly cast buried in the private implementation details.So you want your classes to have private implementation details but you don't want the standard library classes to have private implementation details. Really?
The general problem is as you said vector<A> -> range<B>. Actually my case its even worse because I'm trying to do construct a range<B> sometimes from vector<A> and sometimes from vector<B>, so the hypothetical range type needs to keep track of how it was constructed and do the adaptation as needed.This is generally not possible unless your range object contains a lot of additional logic and references for transparently adapting A to B. This additional logic for doing the adaptation has some performance cost as well as adding complexity to the code. In terms of interface design, returning an array_view<T> is as self-explanatory as it gets. Returning some weird templated range object not as much.My situation is the case is when A is bitwise compatible with B and has the same semantics. In this situation the range can just reinterpret_cast A to B and be iterated over by the called with 0 overhead.For my particular example unique_ptr (with default deleter) is just a raw pointer with some extra type system magic to tell the C++ compiler to generate code to manage the memory for you.A. Nothing in the standard requires that implementation.
B. Classes maintain invariants. You shouldn't be able to legally get around that just by performing a cast, or you'll never be able to reason about the resulting mess.
class Foo {
public:
const auto& getA() { return a; }
const auto& getB() { return b; }
private:
vector<std::unique_ptr<T>> a;
vector<T*> b;
}:
Sorry to beat a probably dead horse, but I really keep running into this problem over and over again. Even if you aren't using array_view, its still a problem.
class Foo {
public:
const auto& getA() { return a; }
const auto& getB() { return b; }
private:
vector<std::unique_ptr<T>> a;
vector<T*> b;
}:
In order to avoid a copy, we're forced to leak the implementation details that A is managed by unique_ptr but B is not. Even if we decide making a copy is ok, this still means that getA() has to return by value while getB() can continue to return by const reference. The ideal scenario which is just returning array_view<T*> is impossible because there is nowhere to store the copy of a.
Both a and b are just vectors of pointers. The fact that a has additional logic tacked on to automatically manage memory is irrelevant to the client of Foo.
This issue is making unique_ptr very cumbersome to use. Ideally I'd like to just be able to specify memory management once within the containing class data members and then pass around array_view<T*> everywhere externally, clients not knowing, caring, or recompiling whether the view points to a set of managed or unmanaged pointers.
class Foo {
public:
array_view<T*> getA() const { return _a.empty() ? {} : { &_a.front()->alias(), _a.size() };
array_view<T*> getB() const { return _b; }
private:
vector<trivial_ptr<T>> _a;
vector<T*> _b;
};
On Monday, February 23, 2015 at 6:52:17 PM UTC-5, Matthew Fioravante wrote:Sorry to beat a probably dead horse, but I really keep running into this problem over and over again. Even if you aren't using array_view, its still a problem.
class Foo {
public:
const auto& getA() { return a; }
const auto& getB() { return b; }
private:
vector<std::unique_ptr<T>> a;
vector<T*> b;
}:
In order to avoid a copy, we're forced to leak the implementation details that A is managed by unique_ptr but B is not. Even if we decide making a copy is ok, this still means that getA() has to return by value while getB() can continue to return by const reference. The ideal scenario which is just returning array_view<T*> is impossible because there is nowhere to store the copy of a.
Both a and b are just vectors of pointers. The fact that a has additional logic tacked on to automatically manage memory is irrelevant to the client of Foo.
But it's very relevant to Foo. And since he's the owner and creator of the array, he's the one who decides these things.
For all intents and purposes, your problem is the lack of modules in C++. Here's what I mean.
What you really want to do is give people a non-modifiable view of your stored data. Your users won't care what specific types you may wrap your stored data in, so long as they obey certain basic principles (ie: the expectations of a pointer to T).
The "correct" way to handle this is with templates. That is, code that wants to consume your array_view would be a template, on the array view's type (or the template type could be more general, a non-modifiable range of pointer-to-T-like objects. Whatever). The problem is that now, all your consuming code needs to live in header files. As much any code that just passes your stuff along to others. Templates are rather viral like that.
If you had modules, you wouldn't have to care. You'd just return an array_view<unique_ptr<T>>, and those who wanted to consume it would be a template that takes an appropriate range. Nobody would be subjected to the details of your memory management (unless they deliberately choose to be). And thanks to modules, they wouldn't be subjected to the horrible compile-times that come along with doing all this (which is the #1 reason why people avoid doing it today. #2 is the lack of concepts).
Basically, this would be a solved problem if compile-time wasn't an issue.
This issue is making unique_ptr very cumbersome to use. Ideally I'd like to just be able to specify memory management once within the containing class data members and then pass around array_view<T*> everywhere externally, clients not knowing, caring, or recompiling whether the view points to a set of managed or unmanaged pointers.
Well, that's just not going to happen. std::unique_ptr does not (necessarily) have zero overhead. It all depends on the deleter.
Plus, that would require aliasing.
The only solution here is a container that specifically handles memory allocation, like Boost's ptr_vector. But I don't think this is a frequent enough problem to have them standardize such a special use container.
But it's very relevant to Foo. And since he's the owner and creator of the array, he's the one who decides these things.Yes that's right, but Foo doesn't have to advertise the fact unless asked for it. Which data is or is not managed may be a part of the Foo specification, but this information is not necessary for clients requesting a const view over the data. The fact that its managed by unique_ptr and not some other unique ownership mechanism is a pure implementation detail which should not be leaked to clients.By returning different types, we make it more difficult to write generic code without templates because we have this artificial dependency leaking all over the place. Even if foo does manage the resources referred to by getA(), the resource management may be performed using another data structure other than the array getA() references. If getA() could return array_view<T*>, we would have better encapsulation and freedom for the implementation of Foo.
For all intents and purposes, your problem is the lack of modules in C++. Here's what I mean.
What you really want to do is give people a non-modifiable view of your stored data. Your users won't care what specific types you may wrap your stored data in, so long as they obey certain basic principles (ie: the expectations of a pointer to T).
The "correct" way to handle this is with templates. That is, code that wants to consume your array_view would be a template, on the array view's type (or the template type could be more general, a non-modifiable range of pointer-to-T-like objects. Whatever). The problem is that now, all your consuming code needs to live in header files. As much any code that just passes your stuff along to others. Templates are rather viral like that.
If you had modules, you wouldn't have to care. You'd just return an array_view<unique_ptr<T>>, and those who wanted to consume it would be a template that takes an appropriate range. Nobody would be subjected to the details of your memory management (unless they deliberately choose to be). And thanks to modules, they wouldn't be subjected to the horrible compile-times that come along with doing all this (which is the #1 reason why people avoid doing it today. #2 is the lack of concepts).
Basically, this would be a solved problem if compile-time wasn't an issue.I haven't studied the latest work in modules yet. If I understand correctly, you're saying with modules basically everything can be a template and whether or not the range iterates over unique_ptr<T> or T* is irrelevant because it will just flow though the type system via type deduction like magic.This works fine until you need to pass the thing to a third party library which views a pointer by taking T* by reference.
It also burdens all of your code with more template syntax when it could just be simple function calls. The more meta-programming you add to your code, the more complex it becomes and the more time you have to spend making things generic.
This issue is making unique_ptr very cumbersome to use. Ideally I'd like to just be able to specify memory management once within the containing class data members and then pass around array_view<T*> everywhere externally, clients not knowing, caring, or recompiling whether the view points to a set of managed or unmanaged pointers.
Well, that's just not going to happen. std::unique_ptr does not (necessarily) have zero overhead. It all depends on the deleter.
If there is a custom or maybe even stateless deleter, I have yet to find a good reason (other than attaching debugging info) to not restrict implementations of unique_ptr to store only a T*. Adding a new type like trivial_ptr<T> seems less optimal than just reusing unique_ptr<T> with a stateless deleter. The alias() call can be conceptified to reject unique_ptr's, with stateful deleters.Plus, that would require aliasing.Ugly scary aliasing is the most efficient way to solve this problem. On modern machines, arrays are almost always the most efficient data structure to store data. array_view<T> brings an extremely flexible range library like solution for this most ubiquitous and efficient data layout, often without requiring templates at all.If you have an array of wrapper<T> stored in memory whose lifetime is managed by a stateless wrapper type, you need aliasing to efficiently retrieve a const view over the array of T.
The only solution here is a container that specifically handles memory allocation, like Boost's ptr_vector. But I don't think this is a frequent enough problem to have them standardize such a special use container.I think the ptr_vector approach is not generic enough. Basically you have to create a copy of the std::vector<T> specification with some customized behavior only for pointers. Lifetime management is fully specified by T so a better solution is just to create a new stateless wrapper type to manage T* and be aliasable to to T*.
Sure, it specifically supports only unique ownership, but... so what?
What other kind of "lifetime management" are you going to be able to do that is both stateless and (theoretically) aliasable to T*?
ptr_vector is specifically designed to solve your exact problem. And it doesn't require breaking strict aliasing (which again, is not gonna happen). So why would you not use it?
template<typename Container>
class unique_container
{
Container c;
public:
template<typename... T>
void emplace_back(T... t) { c.push_back(new Container::value_type(std::forward<T>(t)...)); }
void erase(size_t i) { delete c[i]; c.erase(i); }
const Container& get() const { returns c; }
~unique_container() { for(auto& i : c) { delete i; } }
};
Sure, it specifically supports only unique ownership, but... so what?What other kind of "lifetime management" are you going to be able to do that is both stateless and (theoretically) aliasable to T*?Any stateless memory allocator can be used in this context.