How to make my uninitialised_allocator safe? - c++

Following from this question, I want to use an unitialised_allocator with, say, std::vector to avoid default initialisation of elements upon construction (or resize() of the std::vector (see also here for a use case). My current design looks like this:
// based on a design by Jared Hoberock
template<typename T, typename base_allocator >
struct uninitialised_allocator : base_allocator::template rebind<T>::other
{
// added by Walter Q: IS THIS THE CORRECT CONDITION?
static_assert(std::is_trivially_default_constructible<T>::value,
"value type must be default constructible");
// added by Walter Q: IS THIS THE CORRECT CONDITION?
static_assert(std::is_trivially_destructible<T>::value,
"value type must be default destructible");
using base_t = typename base_allocator::template rebind<T>::other;
template<typename U>
struct rebind
{
typedef uninitialised_allocator<U, base_allocator> other;
};
typename base_t::pointer allocate(typename base_t::size_type n)
{
return base_t::allocate(n);
}
// catch default construction
void construct(T*)
{
// no-op
}
// forward everything else with at least one argument to the base
template<typename Arg1, typename... Args>
void construct(T* p, Arg1 &&arg1, Args&&... args)default_
{
base_t::construct(p, std::forward<Arg1>(arg1), std::forward<Args>(args)...);
}
};
Then an unitialised_vector<> template could be defined like this:
template<typename T, typename base_allocator = std::allocator<T>>
using uninitialised_vector =
std::vector<T,uninitialised_allocator<T,base_allocator>>;
However, as indicated by my comments, I'm not 100% certain as to what are the appropriate conditions in the static_assert()? (Btw, one may consider SFINAE instead -- any useful comments on this are welcome)
Obviously, one has to avoid the disaster that would ensue from the attempted non-trivial destruction of an uninitialised object. Consider
unitialised_vector< std::vector<int> > x(10); // dangerous.
It was suggested (comment by Evgeny Panasyuk) that I assert trivial constructibility, but this does not seem to catch the above disaster scenario. I just tried to check what clang says about std::is_trivially_default_constructible<std::vector<int>> (or std::is_trivially_destructible<std::vector<int>>) but all I got was a crash of clang 3.2 ...
Another, more advanced, option would be to design an allocator which only elides the default construction for objects for which this would be safe to do so.

Fwiw, I think the design can be simplified, assuming a C++11 conforming container:
template <class T>
class no_init_allocator
{
public:
typedef T value_type;
no_init_allocator() noexcept {}
template <class U>
no_init_allocator(const no_init_allocator<U>&) noexcept {}
T* allocate(std::size_t n)
{return static_cast<T*>(::operator new(n * sizeof(T)));}
void deallocate(T* p, std::size_t) noexcept
{::operator delete(static_cast<void*>(p));}
template <class U>
void construct(U*) noexcept
{
static_assert(std::is_trivially_default_constructible<U>::value,
"This allocator can only be used with trivally default constructible types");
}
template <class U, class A0, class... Args>
void construct(U* up, A0&& a0, Args&&... args) noexcept
{
::new(up) U(std::forward<A0>(a0), std::forward<Args>(args)...);
}
};
I see little advantage to deriving from another allocator.
Now you can let allocator_traits handle rebind.
Template the construct members on U. This helps if you want to use this allocator with some container that needs to allocate something other than a T (e.g. std::list).
Move the static_assert test into the single construct member where it is important.
You can still create a using:
template <class T>
using uninitialised_vector = std::vector<T, no_init_allocator<T>>;
And this still fails to compile:
unitialised_vector< std::vector<int> > x(10);
test.cpp:447:17: error: static_assert failed "This allocator can only be used with trivally default constructible types"
static_assert(std::is_trivially_default_constructible<U>::value,
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I think the test for is_trivially_destructible is overkill, unless you also optimize destroy to do nothing. But I see no motivation in doing that since I believe it should get optimized anyway whenever appropriate. Without such a restriction you can:
class A
{
int data_;
public:
A() = default;
A(int d) : data_(d) {}
};
int main()
{
uninitialised_vector<A> v(10);
}
And it just works. But if you make ~A() non trivial:
~A() {std::cout << "~A(" << data_ << ")\n";}
Then, at least on my system, you get an error on construction:
test.cpp:447:17: error: static_assert failed "This allocator can only be used with trivally default constructible types"
static_assert(std::is_trivially_default_constructible<U>::value,
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I.e. A is no longer trivially constructible if it has a non-trivial destructor.
However even with the non-trivial destructor you can still:
uninitialised_vector<A> v;
v.push_back(A());
This works, only because I didn't overreach with requiring a trivial destructor. And when executing this I get ~A() to run as expected:
~A(0)
~A(0)

Related

how to check if type 'T' has 'T(std::initializer_list<U>)' constructor

I have a function func <T> (...) which must be separated into two branches;
1st branch: case when type T have T(std::initializer_list<U>) constructor.
2nd branch: case when type T does not have T(std::initializer_list<U>) constructor.
My current implementation is as follows:
template<typename T, typename U>
using has_init_list_ctor = std::enable_if_t<std::is_constructible_v<
T,
std::initializer_list<U>
>>;
// version for T with initialization list ctor
template<
typename T,
typename = std::enable_if_t<
std::is_detected_v<has_init_list_ctor, T, /* idk how to auto-deduce type U */>
>
>
void func() {
//...
}
// version for T without initialization list ctor
template<
typename T,
typename = std::enable_if_t<
!std::is_detected_v<has_init_list_ctor, T, /* idk how to auto-deduce type U */>
>
>
void func() {
//...
}
But it have a flaw. I have no idea how to auto-deduce type U from T.
the ideal solution would be:
template<typename T>
struct deduce_U_from_T
{
// implementation.
usign type = /* ??? */;
};
template<typename T>
using has_init_list_ctor = std::enable_if_t<std::is_constructible_v<
T,
std::initializer_list<
typename deduce_U_from_T<T>::type
>
>>;
but i have no idea how to implement deduce_U_from_T.
Is there any way to solve this problem?
or is there any workaround?
Update:
Function func <T> (...) is an imitation of std::alocator_traits::construct().
I am trying to implement my own "allocator" for the use of std::vector and smart pointers. Under normal circumstances, I would use the default std::alocator_traits, but this time, I need to request memory from the "special" pool (it is something implemented by me, it can be called "virtual heap", it is accessed via methods like T * get_memory <T> (...), the pool performs additional operations during mem allocation, and offers different "modes" of allocation - I am sorry for being very generic but currently it is WIP, and it constantly changes)
simplistic implementation of func <T> (...) (allocator_traits::construct())
template<typename T>
class allocator_traits
{
//...
public:
template<typename... Args>
static
std::enable_if_t<
std::is_detected_v<has_init_list_ctor, T>,
void
> construct(T * ptr, Args && ... args)
{
new(ptr) T(std::forward<Args>(args)...); // normal brackets // construct with placment-new
}
template<typename... Args>
static
std::enable_if_t<
!std::is_detected_v<has_init_list_ctor, T>,
void
> construct(T * ptr, Args && ... args)
{
new(ptr) T{ std::forward<Args>(args)... }; // curly brackets // construct with placment-new
}
//...
};
The difference lies in the ability to construct type T with curly brackets (when type T doesn't have T(std::initializer_list<U>) constructor.
Is there any way to solve this problem?
Yes. Don't solve it. You shouldn't be trying to guess what kind of initialization the user wants. Just do this:
new (ptr) T(std::forward<Args>(args)...)
If the user wants to use an initializer_list constructor, they can pass in an instance of an initializer_list and that'll work just fine.
The more interesting case is aggregates, which is why they will be initializable with parentheses in C++20 (see P0960). But that can be worked around by passing in an argument which has an appropriate conversion operator. That is, if I want to construct a:
struct X { int i; };
and make that work with parentheses, I can pass in an argument of type:
struct X_factory { int i; operator X() const { return X{i}; } };
and with guaranteed copy elision, we get the right effect anyway.
In any case, initializer_list actually isn't strictly related to the question. What you probably would have wanted (and I wouldn't suggest doing this) is:
if constexpr (std::is_constructible_v<T, Args...>) {
new (ptr) T(std::forward<Args>(args)...);
} else {
new (ptr) T{std::forward<Args>(args)...};
}
Or possibly in the reverse order writing a trait for direct-list-initializable.
#Barry is right, this is a bad idea.
Here is how to do it:
#include <initializer_list>
#include <utility>
#include <iostream>
struct any_il{
template<class U>
operator std::initializer_list<U>()const{ return {}; }
};
struct foo {
foo(std::initializer_list<int>){}
foo(foo&&)=default;
};
template<class T, class=void>
struct can_from_il:std::false_type{};
template<class T>
struct can_from_il<T, decltype(void( T(any_il{}) ) )>:std::true_type{};
int main(){
std::cout << can_from_il<foo>{}() << can_from_il<int>{}() <<"\n";
}
It has many flaws.

How to guarantee copy elision with std::variant?

I have this type:
struct immobile {
// other stuff omitted
immobile(immobile&) = delete;
immobile(immobile&&) = delete;
};
immobile mk_immobile();
// e.g. this compiles
// mk_immobile() is a prvalue and i is its result object
immobile i(mk_immobile());
I also have this class template:
template<typename T>
struct container {
std::variant<T, other_stuff> var;
template<typename... Args>
container(Args&&... args)
: var(std::in_place_index<0>, std::forward<Args>(args)...) {}
};
I want to construct a container around the object produced by mk_immobile(), with the immobile object used to initialize one of the variants of var.
container<immobile> c(mk_immobile());
However, this does not work. For one, std::variant's constructor wants std::is_constructible_v<immobile, immobile>, which doesn't hold. Worse, even this simplified version fails:
template<typename T>
struct demonstration {
T t;
template<typename... Args>
demonstration(Args&&... args) : t(std::forward<Args>(args)...) {}
};
demonstration<immobile> d(mk_immobile());
Which seems to imply that std::forward does not, in fact, perfectly forward—prvalues do not forward as prvalues. (This makes sense to me; I don't think doing that would be possible.) I can make demonstration work by changing it to this:
template<typename T>
struct demonstration {
T t;
template<typename F>
demonstration(F&& f) : t(std::forward<F>(f)()) {}
};
demonstration<immobile> d([] { return mk_immobile(); });
But I do not see a way to change container in a similar manner. How do I change container so that it can construct a std::variant (or other tagged union) out of a prvalue? I can change container but cannot change immobile.
You abuse casts
template<typename F>
struct initializer
{
F f;
template<typename T>
operator T()
{
return f();
}
};
template<typename F>
initializer(F&&) -> initializer<F>;
And use as
container<immobile> c{initializer{[]{
return mk_immobile();
}}};

sfinae away a destructor

I am implementing something very similar to std::vector but uses array on the stack instead of memory allocation.
The d-tor calls a function that uses SFINAE.
If value_type is POD the function have empty body.
If value_type is normal class such std::string, the function have a body and destroy all the data properly.
Now, I want to be able to use this new std::vector as constexpr. However even the c-tor is declared constexpr, the code does not compiles because the class have non trivial d-tor.
Here is small part of the code:
template<typename T, std::size_t SIZE>
class SmallVector{
constexpr SmallVector() = default;
~SmallVector(){
destructAll_<value_type>();
}
// ...
template<typename X>
typename std::enable_if<std::is_trivially_destructible<X>::value == true>::type
destructAll_() noexcept{
}
};
Is there anything I can do to make class be constexpr if value_type is POD and keeping functionality for non POD data types.
(Not at the same time of course)
until C+20
Unfortunately, there is no way to enable/disable destructor with SFINAE, nor with future concepts. That is because destructos:
can't be templated
can't have arguments
can't have a return type
What you can do is specialize whole class, or better yet, create a base class that contains only the construct/destruct and basic access and specialize that.
template <class T, class Enable = void>
struct X {
~X() {}
};
template <class T>
struct X<T, std::enable_if_t<std::is_pod<T>::value>> {
};
static_assert(std::is_trivially_destructible<X<int>>::value);
static_assert(!std::is_trivially_destructible<X<std::vector<int>>>::value);
C++ 20
As far as I can tell you can constraint a destructor and get exactly what you want in a very simple and elegant solution:
template<typename T, std::size_t SIZE>
class SmallVector{
public:
constexpr SmallVector() = default;
~SmallVector() requires std::is_trivially_destructible_v<T> = default;
~SmallVector()
{
}
};
static_assert(std::is_trivially_destructible_v<SmallVector<int, 4>>);
static_assert(!std::is_trivially_destructible_v<SmallVector<std::string, 4>>);
However this is a brand new feature and there have been some changes lately (e.g. see Disable non-templated methods with concepts) and the compiler support is still sketchy. gcc compiles this just fine, while clang is confused by the fact that there are two definitions of the destructor godbolt
The example of if constexpr in destructor. (C++17 required)
template<typename Tp, typename TLock>
struct LockedPtr {
private:
Tp *m_ptr;
TLock *m_lk;
void prelock(std::mutex *mtx) { mtx->lock(); }
void prelock(std::atomic_flag *atom) { while(atom->test_and_set(std::memory_order_acquire)); }
public:
LockedPtr(Tp *ptr, TLock *mtx)
: m_ptr(ptr), m_lk(mtx) {
prelock(mtx);
}
~LockedPtr() {
if constexpr (std::is_same_v<TLock, std::mutex>)
((std::mutex *)m_lk)->unlock();
if constexpr (std::is_same_v<TLock, std::atomic_flag>)
((std::atomic_flag *)m_lk)->clear(std::memory_order_release);
}
};
These code is the part of RAII locked smart pointer, to adopt to normal std::mutex and spinlock by std::atomic_flag.
Using function overload to match different type in constructor.
Match type by if constexpr and make something unconvertable to pointer in destructor.

Trouble with storing a type tag when implementing an std::variant-like class

My aim is to write std::variant, may be not full blown, but at least with fully working constructor/destructor pair and std::get<>() function.
I tried to reserve a memory using char array. The size of it is determined by the biggest type, which is found by using find_biggest_size<>() function. The constructor uses static assert, because it performs check if the type is in the list of specified types. For now, the constructor and in place constructor works.
template <typename ... alternatives>
class variant
{
char object[find_biggest_size<alternatives...>::value];
public:
template <typename T>
variant(T&& other)
{
static_assert(is_present<T, alternatives...>::value, "type is not in range");
new ((T*)&object[0]) T(std::forward<T>(other));
}
template <typename T, typename ... ArgTypes>
variant(in_place_t<T>, ArgTypes&& ... args)
{
static_assert(is_present<T, alternatives...>::value, "type is not in range");
new ((T*)&object[0]) T(std::forward<ArgTypes>(args)...);
}
~variant()
{
// what to do here?
}
};
Then I've stumbled upon a problem. I don't know what destructor to execute when the object dies. On top of that, it is impossible to access the underlying object, since I can't specialize std::get<>() to get the right type.
My question is: how to store the type after the creation of the object? Is it the right approach? If not, what should I use?
EDIT:
I tried to apply the comments. The problem is that the index of the type that is currently alive can't be constexpr, thus I can't extract the needed type from type list and invoke appropriate destructor.
~variant()
{
using T = typename extract<index, alternatives...>::type;
(T*)&object[0]->~T();
}
EDIT:
I've made a baseline implementation. It works, but has lots of missing features. You can find it here. I would be glad to receive a review, but please first read how do I write a good answer?.
How I'd probably start:
#include <iostream>
#include <utility>
#include <array>
template<class...Types>
struct variant
{
variant() {}
~variant()
{
if (type_ >= 0)
{
invoke_destructor(type_, reinterpret_cast<char*>(std::addressof(storage_)));
}
}
template<class T> static void invoke_destructor_impl(char* object)
{
auto pt = reinterpret_cast<T*>(object);
pt->~T();
}
static void invoke_destructor(int type, char* address)
{
static const std::array<void (*)(char*), sizeof...(Types)> destructors
{
std::addressof(invoke_destructor_impl<Types>)...
};
destructors[type](address);
}
std::aligned_union_t<0, Types...> storage_;
int type_ = -1;
};
int main()
{
variant<int, std::string> v;
}
First of all, you need to know which object is currently in the variant. If you want to get a type from it, that is not currently in it, you must throw an exception.
For the storage I use a union (as I do here to make it constexpr); you can't use the placement new operator as a constexpr so I think the union is the only actual way to do that (which means the only one I came up with). Mind you: you still need to explicitly call the destructor. Which yields the strange workaround I have, because a type used in a constexpr must be trivially destructible.
Now: you can implement a class similar to find_biggest_size which gives you the type from an int as a template parameter. I.e. something like that (incomplete example) :
template<int idx, typename ...Args>
struct get_type;
template<int idx, typename First, typename ...Rest>
struct get_type<idx, First, Rest...>
{
using type = typename get_type<idx-1, Rest>::type;
};
template<typename First, typename ...Rest>
struct get_type<0, First, Rest...>
{
using type = First;
};
//plus specialization without Rest
And then you can implement the get function:
template<int i, typename ...Args>
auto get(variant<Args...> & v) -> typename get_type<i, Args...>::type
{ /* however you do that */ }
I hope that helps.

Template for classes with various signatures of constructors

How to write a template, that would take as argument classes, whose constructors have mutually exclusive signatures?
class A
{
A(){};
public:
int a;
A(int i) : a(i) {};
};
class B{
B(){};
public:
int a,b;
B(int i,int j) : a(i), b(j) {};
};
template <class T> class C {
public:
T* _t;
C(int i[])
{ //???
_t=new T(i[0]); //if T has T(int) signature
_t=new T(i[0],i[1]); //if T has T(int,int) signature
}
~C() {delete _t;}
};
int main()
{
int Ai[]={1,2};
C<A> _c(Ai); // template should work instantiated with A and B
C<B> _c(Ai); //
return 0;
}
The signatures of A and B are fixed (cannot be changed to int[] e.g.). Context: I'm thinking about a wrapper, that would take a (specialized) container type as the template argument, e.g. T=vector<int>, or T=map<int,int>, and the problem arises when constructors need to be called.
Use a variadically-templated constructor:
template <typename T> struct C
{
template <typename ...Args> C(Args &&... args)
: _t(new T(std::forward<Args>(args)...))
{
}
// ... destructor? Rule of five? Don't use pointers!!!
private:
T * _t; // ouch!
};
Usage:
C<A> x(1);
C<B> y(2, 3);
(Real programmers would of course prefer a member std::unique_ptr<T> _t;, with otherwise unchanged semantics, but allowing you to disregard all the comments.)
I believe Kerrek SB's answer is partially right, but incomplete. It fails in that C<T>'s constructor is overly generic. That is, C<T> will construct from anything if you just look at its constructor declaration. You don't find out otherwise until you've selected the constructor and you're instantiating. And by then it is too late.
Concrete example:
Let's say that C<T> has:
friend bool operator<(const C&, const C&);
And now you want to make C<T> the key in a map:
std::map<C<A>, int> m;
// ...
m.erase(m.begin());
This is an error because there are two erase overloads which now look like:
iterator erase(const_iterator position);
size_type erase(const key_type& k);
and m.begin() is an iterator. This iterator will convert with equal ease to both const_iterator and key_type (aka C<A>).
Now this can be fixed by calling:
m.erase(m.cbegin());
instead. But this is just the tip of the iceberg on problems that overly generic constructors cause. For example any code that branches on:
std::is_constructible<C<A>, any type and any number of them>::value
is likely to get false positives because the above will always return true.
The fix is a little messy, but very practical:
template<typename T>
struct C
{
template <class ...Args,
class = typename std::enable_if
<
std::is_constructible<T, Args...>::value
>::type
>
C(Args&& ...args)
: _t(new T(std::forward<Args>(args)...))
{
}
// ...
};
I.e. Add a constraint to the constructor such that it won't be instantiated if it isn't going to work. This is messy, ugly, whatever. Perhaps you want to dress it up with a macro. Fine. But it makes this class work where otherwise it is broken in the examples I mention above (and numerous other problems that tend to trickle in as bug reports one at a time over a period of years).
In addition to Kerrek SB's good advice on using unique_ptr<T> over a raw pointer here, I would like to add:
That this constructor should probably be explicit, at least until it is shown by actual use cases that it really needs to be implicit.
Consider storing a T instead of a (possibly smart) pointer to T. You don't need pointer semantics unless you're actually trying to point to a base class to achieve run-time polymorphism.
In summary: be wary of overly generic code, and downright paranoid of overly generic constructors.