Exception specifications for vector modifiers

98 views
Skip to first unread message

thedownsideo...@gmail.com

unread,
Nov 3, 2013, 3:42:21 PM11/3/13
to std-dis...@isocpp.org
Hi,

I'm wondering why the exception specifications for vector::insert and other modifiers are formulated in terms of the value/element type T instead of in terms of allocator functions.

Referring to the latest draft in the github repository (from 2013-10-30, revising N3797), [vector.modifiers]/1:

If an exception is thrown other than by the copy constructor, move constructor, assignment operator, or move assignment operator of T or by any InputIterator operation there are no effects. If an exception is thrown by the move constructor of a non-CopyInsertable T, the effects are unspecified.

[Note this paragraph is subject to DR2252.]

The part about assignment is clear to me, but [container.requirements.general]/3 requires the use of allocator_traits<allocator_type>::construct to create object stored in a vector. Do the remaining requirements therefore refer to allocator_traits<allocator_type>::construct, e.g. "copy constructor of T" referring to a call to allocator_traits<allocator_type>::construct with a const lvalue of type T as a third argument?

I will try to formulate my question/concern as an example. Considering the following type:

struct some_type
{
   
int m;
   
    some_type
(int p) : m(p) {}
    some_type
(some_type const&) = delete; // irrelevant
    some_type
(some_type&&) noexcept;
   
    some_type
(int i, some_type&& rhs)
   
: m( rhs.m )
   
{
        rhs
.m = -1; // violate the strong guarantee
       
if(i == 42) throw "some_type -- i==42";
   
}
   
   
~some_type() = default;
   
    some_type
& operator=(some_type const&) = default;
    some_type
& operator=(some_type&&) = default;
};

I think the following type evil_allocator is a valid allocator type, e.g. for vector<some_type, evil_allocator>:

struct evil_allocator
{
   
typedef some_type value_type;
   
    some_type
*allocate(std::size_t n);
   
void deallocate(some_type *p, std::size_t n);
   
   
void construct(some_type* p, some_type&& r)
   
{
       
static int i = 0;
        some_type temp
(i++, std::move(r));
       
// violate the strong guarantee
       
if(r.m == 41) throw "evil_allocator -- r.m==41";
       
new((void*)p) some_type( std::move(temp) );
   
}
   
   
void construct(some_type* p, int i)
   
{
       
new((void*)p) some_type(i);
   
}
};
bool operator==(const evil_allocator&, const evil_allocator&);
bool operator!=(const evil_allocator&, const evil_allocator&);

Note: some_type is not CopyInsertable via evil_allocator (nor via std::allocator<some_type>) but this should be irrelevant. The deleted copy ctor just prohibits using copy construction, as this example is written for move construction.
The move constructor of some_type is noexcept; some_type is not DefaultInsertable but EmplaceConstructible and MoveInsertable for evil_allocator; it is CopyAssignable and MoveAssignable.

My question/concern:
If evil_allocator is a valid allocator, and the requirements in [vector.modifiers]/1 apply to T (here: some_type) instead of allocator_traits<evil_allocator>::construct, wouldn't the implementation be required to provide a strong guarantee ("there are no effects") for vector::insert etc., even if evil_allocator::construct or some_type::some_type(int, some_type&&) throws an exception?

Looking into libstdc++ and performing some tests seems to indicate there's indeed no strong guarantee for this implementation and the example shown above. The check is_nothrow_move_constructible is applied directly to the value type, ignoring the allocator.

Slightly unrelated: I guess "there are no effects" means "there are no effects for the container object", consider istream_iterator for the range overload vector::insert(const_iterator, InputIterator, InputIterator).


Thank you very much for any clarification.
Reply all
Reply to author
Forward
0 new messages