vector.emplace(vector.begin(), vector.back())

511 views
Skip to first unread message

Nikolay Ivchenkov

unread,
Jul 6, 2012, 4:25:42 PM7/6/12
to std-dis...@isocpp.org
Is the following program well-defined?

    #include <iostream>
    #include <vector>

    int main()
    {
        std::vector<int> v;
        v.reserve(4);
        v = { 1, 2, 3 };
        v.emplace(v.begin(), v.back());
        for (int x : v)
            std::cout << x << std::endl;
    }

I think that an implementation of vector's 'emplace' should initialize an intermediate object with v.back() before any shifts take place, then perform all necessary shifts and finally replace the value pointed to by v.begin() with the value of the intermediate object. So, I would expect the following output:

    3
    1
    2
    3

GNU C++ 4.7.1 and GNU C++ 4.8.0 produce other results:

    2
    1
    2
    3

Do we have a bug here?

Chris Jefferson

unread,
Jul 6, 2012, 5:16:17 PM7/6/12
to std-dis...@isocpp.org
On 06/07/12 21:25, Nikolay Ivchenkov wrote:
> Is the following program well-defined?
>
> #include <iostream>
> #include <vector>
>
> int main()
> {
> std::vector<int> v;
> v.reserve(4);
> v = { 1, 2, 3 };
> v.emplace(v.begin(), v.back());
> for (int x : v)
> std::cout << x << std::endl;
> }
>
> I think that an implementation of vector's 'emplace' should initialize
> an intermediate object with v.back() before any shifts take place,
> then perform all necessary shifts and finally replace the value
> pointed to by v.begin() with the value of the intermediate object. So,
> I would expect the following output:

Certainly, we should either be clear this is forbidden, or allow it.

My reading of the current standard is that indeed implementations have a
bug. While the whole purpose of emplace methods is efficiency, and
reducing copying, in the case of vector::emplace constructing the
intermediate value doesn't seem like it would be too expensive, as we
already have to move around various elements in the vector anyway.

However, if you are happy with an extra move, there is always an
argument you could use insert/push_back instead of emplace/emplace_back,
users who use emplace are typically doing it for efficiency.


Chris


Nevin Liber

unread,
Jul 6, 2012, 5:26:13 PM7/6/12
to std-dis...@isocpp.org
On 6 July 2012 16:16, Chris Jefferson <ch...@bubblescope.net> wrote:
> However, if you are happy with an extra move, there is always an argument
> you could use insert/push_back instead of emplace/emplace_back, users who
> use emplace are typically doing it for efficiency.

For vector, I agree. For the other containers, most of the emplace*
methods can be used for storing non-moveable non-copyable types.
--
Nevin ":-)" Liber <mailto:ne...@eviloverlord.com> (847) 691-1404

Chris Jefferson

unread,
Jul 6, 2012, 5:33:13 PM7/6/12
to std-dis...@isocpp.org
On 06/07/12 22:26, Nevin Liber wrote:
> On 6 July 2012 16:16, Chris Jefferson <ch...@bubblescope.net> wrote:
>> However, if you are happy with an extra move, there is always an argument
>> you could use insert/push_back instead of emplace/emplace_back, users who
>> use emplace are typically doing it for efficiency.
> For vector, I agree. For the other containers, most of the emplace*
> methods can be used for storing non-moveable non-copyable types.
True. However, fortunately, I believe the emplace* methods which would
need to make an object and then move it to fix this bug (I believe the
issues are vector::emplace, vector::emplace_back and deque::emplace),
all already require move-constructability. This of course is not
surprising, as these are the methods which are moving values around,
causing problems.

Chris

Howard Hinnant

unread,
Jul 6, 2012, 5:52:32 PM7/6/12
to std-dis...@isocpp.org
On Jul 6, 2012, at 5:16 PM, Chris Jefferson wrote:

> However, if you are happy with an extra move, there is always an argument you could use insert/push_back instead of emplace/emplace_back, users who use emplace are typically doing it for efficiency.

Strongly agree. Let's not slow down everyone's emplace to make happy a client who should be using insert.

Howard

Nevin Liber

unread,
Jul 6, 2012, 6:11:03 PM7/6/12
to std-dis...@isocpp.org
On 6 July 2012 16:16, Chris Jefferson <ch...@bubblescope.net> wrote:
> However, if you are happy with an extra move, there is always an argument
> you could use insert/push_back instead of emplace/emplace_back, users who
> use emplace are typically doing it for efficiency.

I'm going to disagree with that. People are moving to emplace* over
push_*/insert in general. For instance, to store a value into a map
there is a lot less typing to say:

m.emplace(key, value);

over

m.insert(make_pair(key, value)); // or equivalent

You can even find the recommendation to prefer emplacement over
insertion here:
<http://cppandbeyond.com/2012/04/16/session-topic-initial-thoughts-on-effective-c11/>.

I'm not sure how that affects the efficiency argument as long as we
have correctness first (and the case in the subject should be allowed,
unless you can ban it at compile time).

Nikolay Ivchenkov

unread,
Jul 7, 2012, 5:46:05 PM7/7/12
to std-dis...@isocpp.org

Are you suggesting to cancel existing guarantees?

BTW, creating an object at the point of insertion other than end would be tricky: we have to destroy an existing object at that point in the vector and ensure that either the hole will be filled somehow (note that an object construction may fail) or all subsequent items will be removed (I would find such behavior silly).

Howard Hinnant

unread,
Jul 7, 2012, 5:55:23 PM7/7/12
to std-dis...@isocpp.org
On Jul 7, 2012, at 5:46 PM, Nikolay Ivchenkov wrote:

> On Saturday, July 7, 2012 1:52:32 UTC+4, Howard Hinnant wrote:
>> On Jul 6, 2012, at 5:16 PM, Chris Jefferson wrote:
>>
>> > However, if you are happy with an extra move, there is always an argument you could use insert/push_back instead of emplace/emplace_back, users who use emplace are typically doing it for efficiency.
>>
>> Strongly agree. Let's not slow down everyone's emplace to make happy a client who should be using insert.
>>
> Are you suggesting to cancel existing guarantees?

I'm suggesting that the LWG had a direction on this and punted simply because it couldn't come up with the right words. That direction is what the implementors then coded:

http://cplusplus.github.com/LWG/lwg-closed.html#760

I've requested the LWG re-open LWG 760 and address it. I've also suggested that your question be added to the issue.

> BTW, creating an object at the point of insertion other than end would be tricky: we have to destroy an existing object at that point in the vector and ensure that either the hole will be filled somehow (note that an object construction may fail) or all subsequent items will be removed (I would find such behavior silly).

This is how libc++ implements it:

template <class _Tp, class _Allocator>
template <class... _Args>
typename vector<_Tp, _Allocator>::iterator
vector<_Tp, _Allocator>::emplace(const_iterator __position, _Args&&... __args)
{
pointer __p = this->__begin_ + (__position - begin());
if (this->__end_ < this->__end_cap())
{
if (__p == this->__end_)
{
__alloc_traits::construct(this->__alloc(),
_VSTD::__to_raw_pointer(this->__end_),
_VSTD::forward<_Args>(__args)...);
++this->__end_;
}
else
{
__move_range(__p, this->__end_, __p + 1);
*__p = value_type(_VSTD::forward<_Args>(__args)...); // <----- here
}
}
else
{
allocator_type& __a = this->__alloc();
__split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), __p - this->__begin_, __a);
__v.emplace_back(_VSTD::forward<_Args>(__args)...);
__p = __swap_out_circular_buffer(__v, __p);
}
return __make_iter(__p);
}

Howard

Nikolay Ivchenkov

unread,
Jul 8, 2012, 5:23:59 AM7/8/12
to std-dis...@isocpp.org
On Sunday, July 8, 2012 1:55:23 AM UTC+4, Howard Hinnant wrote:
On Jul 7, 2012, at 5:46 PM, Nikolay Ivchenkov wrote:
> Are you suggesting to cancel existing guarantees?

I'm suggesting that the LWG had a direction on this and punted simply because it couldn't come up with the right words.

So programmers and implementors are supposed to follow informal directions of LWG instead of formal guarantees specified by normative rules? I just want to understand the politics of the Committee.
 
I've requested the LWG re-open LWG 760 and address it.  I've also suggested that your question be added to the issue.

Yes, I saw c++std-lib-32772. IMHO, it's too late to introduce new constraints now.
 
> BTW, creating an object at the point of insertion other than end would be tricky: we have to destroy an existing object at that point in the vector and ensure that either the hole will be filled somehow (note that an object construction may fail) or all subsequent items will be removed (I would find such behavior silly).

This is how libc++ implements it:

gcc-4.8-20120701 provides the following implementation (uninteresting branches are skipped):

      _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
                       _GLIBCXX_MOVE(*(this->_M_impl._M_finish
                                   - 1)));
      ++this->_M_impl._M_finish;
      _GLIBCXX_MOVE_BACKWARD3(__position.base(),
                  this->_M_impl._M_finish - 2,
                  this->_M_impl._M_finish - 1);
      *__position = _Tp(std::forward<_Args>(__args)...);

If the intermediate object would be created at the right moment

      _Tp __tmp(std::forward<_Args>(__args)...);
      _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
                       _GLIBCXX_MOVE(*(this->_M_impl._M_finish
                                   - 1)));
      ++this->_M_impl._M_finish;
      _GLIBCXX_MOVE_BACKWARD3(__position.base(),
                  this->_M_impl._M_finish - 2,
                  this->_M_impl._M_finish - 1);
      *__position = std::move(__tmp);

we would get correct results in cases like above.

Nevin Liber

unread,
Jul 8, 2012, 1:00:52 PM7/8/12
to std-dis...@isocpp.org
On 8 July 2012 04:23, Nikolay Ivchenkov <ts...@mail.ru> wrote:
> On Sunday, July 8, 2012 1:55:23 AM UTC+4, Howard Hinnant wrote:
>> I've requested the LWG re-open LWG 760 and address it. I've also
>> suggested that your question be added to the issue.
>
>
> Yes, I saw c++std-lib-32772. IMHO, it's too late to introduce new
> constraints now.

+1

I find this pattern of looking to change the library in ways that
silently break user code to be disturbing.

Howard Hinnant

unread,
Jul 8, 2012, 1:04:50 PM7/8/12
to std-dis...@isocpp.org
On Jul 8, 2012, at 1:00 PM, Nevin Liber wrote:

> On 8 July 2012 04:23, Nikolay Ivchenkov <ts...@mail.ru> wrote:
>> On Sunday, July 8, 2012 1:55:23 AM UTC+4, Howard Hinnant wrote:
>>> I've requested the LWG re-open LWG 760 and address it. I've also
>>> suggested that your question be added to the issue.
>>
>>
>> Yes, I saw c++std-lib-32772. IMHO, it's too late to introduce new
>> constraints now.
>
> +1
>
> I find this pattern of looking to change the library in ways that
> silently break user code to be disturbing.

Does there exist an implementation which does something different than libc++ or gcc? If not, then what user code will be broken if we standardize existing behavior?

What does VC++XX do?

Howard

Ville Voutilainen

unread,
Jul 8, 2012, 1:22:18 PM7/8/12
to std-dis...@isocpp.org
Excuse me, but where's the rvalue reference that
http://cplusplus.github.com/LWG/lwg-closed.html#760 speaks
of? The example in the beginning of the thread uses .back(), which
returns an lvalue reference, not an rvalue reference.
To me, it would make sense for that example to work. I don't see how
it could work for non-copyable/movable types,
because it certainly seems to try and invoke copy construction, since
it provides a constructor argument that's
an lvalue reference to an object of the type being constructed.

I guess I can stomach a "with emplace, be careful not to emplace from
container elements", but the example
seems quite innocent to me, and it's a bit hard to explain exactly why
the container gets messed up, since
there shouldn't be any moving going on.

Howard Hinnant

unread,
Jul 8, 2012, 1:53:49 PM7/8/12
to std-dis...@isocpp.org
On Jul 8, 2012, at 1:22 PM, Ville Voutilainen wrote:

> On 8 July 2012 20:04, Howard Hinnant <howard....@gmail.com> wrote:
>> On Jul 8, 2012, at 1:00 PM, Nevin Liber wrote:
>>> On 8 July 2012 04:23, Nikolay Ivchenkov <ts...@mail.ru> wrote:
>>>> On Sunday, July 8, 2012 1:55:23 AM UTC+4, Howard Hinnant wrote:
>>>>> I've requested the LWG re-open LWG 760 and address it. I've also
>>>>> suggested that your question be added to the issue.
>>>> Yes, I saw c++std-lib-32772. IMHO, it's too late to introduce new
>>>> constraints now.
>>> +1
>>> I find this pattern of looking to change the library in ways that
>>> silently break user code to be disturbing.
>> Does there exist an implementation which does something different than libc++ or gcc? If not, then what user code will be broken if we standardize existing behavior?
>> What does VC++XX do?
>
> Excuse me, but where's the rvalue reference that
> http://cplusplus.github.com/LWG/lwg-closed.html#760 speaks
> of?

[container.requirements]/12 was existing wording when this proposed wording was drafted. It appears in the issue only to say where the proposed wording should go. The intent of what was [container.requirements]/12 now lives at [res.on.arguments]/p1/b3.

> The example in the beginning of the thread uses .back(), which
> returns an lvalue reference, not an rvalue reference.
> To me, it would make sense for that example to work. I don't see how
> it could work for non-copyable/movable types,
> because it certainly seems to try and invoke copy construction, since
> it provides a constructor argument that's
> an lvalue reference to an object of the type being constructed.
>
> I guess I can stomach a "with emplace, be careful not to emplace from
> container elements", but the example
> seems quite innocent to me, and it's a bit hard to explain exactly why
> the container gets messed up, since
> there shouldn't be any moving going on.

There is precedent for the "be careful" spec: Table 100, a.insert(p,i,j):

pre: i and j are not iterators into a.

Further info: libc++ vector::insert makes this case work and *not* by making a temporary copy (which has unknown expense). Instead it detects self referencing and recomputes the reference when detected:

template <class _Tp, class _Allocator>
typename vector<_Tp, _Allocator>::iterator
vector<_Tp, _Allocator>::insert(const_iterator __position, const_reference __x)
{
pointer __p = this->__begin_ + (__position - begin());
if (this->__end_ < this->__end_cap())
{
if (__p == this->__end_)
{
__alloc_traits::construct(this->__alloc(),
_VSTD::__to_raw_pointer(this->__end_), __x);
++this->__end_;
}
else
{
__move_range(__p, this->__end_, __p + 1);
const_pointer __xr = pointer_traits<const_pointer>::pointer_to(__x);
if (__p <= __xr && __xr < this->__end_)
++__xr; // recompute reference
*__p = *__xr;
}
}
else
{
allocator_type& __a = this->__alloc();
__split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), __p - this->__begin_, __a);
__v.push_back(__x);
__p = __swap_out_circular_buffer(__v, __p);
}
return __make_iter(__p);
}

I believe this technique to be superior to that of making a local temporary (not for vector<int>, but for vector<expensive-to-copy>). In general, libc++ goes to extra effort to avoid gratuitous copies and even moves.

If the LWG clarifies this case such that it should work as Nikolay prefers, I would like to be able to implement it by forwarding this specific case to vector::insert with a compile-time switch.

template <class _Tp, class _Allocator>
template <class... _Args>
inline
typename vector<_Tp, _Allocator>::iterator
vector<_Tp, _Allocator>::emplace(const_iterator __position, value_type& __x)
{
return insert(__position, __x);
}

template <class _Tp, class _Allocator>
template <class... _Args>
inline
typename vector<_Tp, _Allocator>::iterator
vector<_Tp, _Allocator>::emplace(const_iterator __position, const value_type& __x)
{
return insert(__position, __x);
}

Such a solution will not address all of the cases LWG 760 refers to. But it will only penalize those clients who should be using vector::insert anyway.

Anyone have the behavior of VC++ for Nikolay's example?

#include <iostream>
#include <vector>

int main()
{
std::vector<int> v;
v.reserve(4);
v = { 1, 2, 3 };
v.emplace(v.begin(), v.back());
for (int x : v)
std::cout << x << std::endl;
}

Howard

Vlad from Moscow

unread,
Jul 8, 2012, 2:21:39 PM7/8/12
to std-dis...@isocpp.org

воскресенье, 8 июля 2012 г., 21:53:49 UTC+4 пользователь Howard Hinnant написал:
Anyone have the behavior of VC++ for Nikolay's example?

    #include <iostream>
    #include <vector>

    int main()
    {
        std::vector<int> v;
        v.reserve(4);
        v = { 1, 2, 3 };
        v.emplace(v.begin(), v.back());
        for (int x : v)
            std::cout << x << std::endl;
    }

Howard
MS VC++ 2010 gives 3 1 2 3
 
 

Ville Voutilainen

unread,
Jul 8, 2012, 3:41:39 PM7/8/12
to std-dis...@isocpp.org
On 8 July 2012 20:53, Howard Hinnant <howard....@gmail.com> wrote:
> There is precedent for the "be careful" spec: Table 100, a.insert(p,i,j):

Duly noted. I'm not strongly opposed to doing that for emplace too. I have
a slight preference towards having fewer be-careful-cases, and I think
I'd prefer not having to be that careful with insert, even if I need to be
careful with emplace.

> Such a solution will not address all of the cases LWG 760 refers to. But it will only penalize those clients who should be using vector::insert anyway.

Sounds good to me. Any idea about the severity of the penalty?

Howard Hinnant

unread,
Jul 8, 2012, 3:51:14 PM7/8/12
to std-dis...@isocpp.org
Thanks. Now we know that no matter what the LWG decides, existing code is potentially broken.

Howard

Ville Voutilainen

unread,
Jul 8, 2012, 3:53:12 PM7/8/12
to std-dis...@isocpp.org
On 8 July 2012 22:51, Howard Hinnant <howard....@gmail.com> wrote:
>> воскресенье, 8 июля 2012 г., 21:53:49 UTC+4 пользователь Howard Hinnant написал:
>> Anyone have the behavior of VC++ for Nikolay's example?
>> MS VC++ 2010 gives 3 1 2 3
> Thanks. Now we know that no matter what the LWG decides, existing code is potentially broken.

And on the other hand, we have conflicting existing behavior, so the
LWG should indeed make
some sort of a decision. :)

Nevin Liber

unread,
Jul 8, 2012, 4:44:42 PM7/8/12
to std-dis...@isocpp.org
On 8 July 2012 14:53, Ville Voutilainen <ville.vo...@gmail.com> wrote:
> And on the other hand, we have conflicting existing behavior, so the
> LWG should indeed make
> some sort of a decision. :)

Only if the conflicting behavior is due to a bug in the standard and
not due to a bug in one of the implementations.

Nikolay Ivchenkov

unread,
Jul 8, 2012, 5:10:47 PM7/8/12
to std-dis...@isocpp.org
On Sunday, July 8, 2012 9:04:50 PM UTC+4, Howard Hinnant wrote:

Does there exist an implementation which does something different than libc++ or gcc?

I didn't see any conforming implementation of vector's emplace.
 
 If not, then what user code will be broken if we standardize existing behavior?

How soon will the nearest TC come?

What does VC++XX do?

I wouldn't consider VC++10 seriously. It doesn't support variadic templates.

    template<class _Valty>
        iterator insert(const_iterator _Where, _Valty&& _Val)
        {    // insert _Val at _Where
        return (emplace(_Where, _STD forward<_Valty>(_Val)));
        }

    template<class _Valty>
        iterator emplace(const_iterator _Where, _Valty&& _Val)
        {    // insert _Val at _Where
        size_type _Off = _VIPTR(_Where) - this->_Myfirst;

    #if _ITERATOR_DEBUG_LEVEL == 2
        if (size() < _Off)
            _DEBUG_ERROR("vector emplace iterator outside range");
    #endif /* _ITERATOR_DEBUG_LEVEL == 2 */

        emplace_back(_STD forward<_Valty>(_Val));
        _STD rotate(begin() + _Off, end() - 1, end());
        return (begin() + _Off);
        }

This implementation is broken anyway.

    #include <vector>

    struct X {};
    template <class T>
        void swap(T &, T &);

    int main()
    {
        std::vector<int *> v1;
        v1.insert(v1.end(), 0);    // doesn't compile
        std::vector<X> v2;
        v2.emplace(v2.end(), X()); // doesn't compile
    }

VC++11 is a better candidate for consideration, but I don't have it.

Howard Hinnant

unread,
Jul 8, 2012, 7:23:05 PM7/8/12
to std-dis...@isocpp.org
On Jul 8, 2012, at 3:41 PM, Ville Voutilainen wrote:

> Sounds good to me. Any idea about the severity of the penalty?

I've convinced myself that I was incorrect about the penalty and that Nikolay's implementation:

On Jul 8, 2012, at 5:23 AM, Nikolay Ivchenkov wrote:

> If the intermediate object would be created at the right moment
>
> _Tp __tmp(std::forward<_Args>(__args)...);
> _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
> _GLIBCXX_MOVE(*(this->_M_impl._M_finish
> - 1)));
> ++this->_M_impl._M_finish;
> _GLIBCXX_MOVE_BACKWARD3(__position.base(),
> this->_M_impl._M_finish - 2,
> this->_M_impl._M_finish - 1);
> *__position = std::move(__tmp);

imposes no penalty at all (except in an exceptional case which nobody cares about). In either implementation a temporary is constructed and a move assignment is done from that temporary. Additionally this solution addresses *all* of the use cases to which lwg 760 refers.

I've modified the libc++ vector and deque to follow this strategy.

I still think we need to open LWG 760 and put a clarification in the standard.

I'm curious if other implementations get:

3
1
2
3

in the *insufficient* capacity case:

#include <iostream>
#include <vector>

int main()
{
std::vector<int> v;
v.reserve(3); // was 4 now 3
v = { 1, 2, 3 };
v.insert(v.begin(), v.back());
for (int x : v)
std::cout << x << std::endl;
}

On Jul 8, 2012, at 5:10 PM, Nikolay Ivchenkov wrote:

> How soon will the nearest TC come?

I don't think anyone knows at this point. The committee is talking about a 2017 standard (not just a TC).

> What does VC++XX do?
>
> I wouldn't consider VC++10 seriously. It doesn't support variadic templates.
>
> template<class _Valty>
> iterator insert(const_iterator _Where, _Valty&& _Val)
> { // insert _Val at _Where
> return (emplace(_Where, _STD forward<_Valty>(_Val)));
> }
>
> template<class _Valty>
> iterator emplace(const_iterator _Where, _Valty&& _Val)
> { // insert _Val at _Where
> size_type _Off = _VIPTR(_Where) - this->_Myfirst;
>
> #if _ITERATOR_DEBUG_LEVEL == 2
> if (size() < _Off)
> _DEBUG_ERROR("vector emplace iterator outside range");
> #endif /* _ITERATOR_DEBUG_LEVEL == 2 */
>
> emplace_back(_STD forward<_Valty>(_Val));
> _STD rotate(begin() + _Off, end() - 1, end());
> return (begin() + _Off);
> }

This looks like a reasonable implementation sans compiler support for variadics.

>
> This implementation is broken anyway.
>
> #include <vector>
>
> struct X {};
> template <class T>
> void swap(T &, T &);
>
> int main()
> {
> std::vector<int *> v1;
> v1.insert(v1.end(), 0); // doesn't compile

This compiles for me.

> std::vector<X> v2;
> v2.emplace(v2.end(), X()); // doesn't compile
> }

This doesn't:

error: call to 'swap' is ambiguous. That error message looks correct to me.

Howard


Nikolay Ivchenkov

unread,
Jul 9, 2012, 4:08:23 AM7/9/12
to std-dis...@isocpp.org
On Monday, July 9, 2012 3:23:05 AM UTC+4, Howard Hinnant wrote:

I've convinced myself that I was incorrect about the penalty and that Nikolay's implementation:

On Jul 8, 2012, at 5:23 AM, Nikolay Ivchenkov wrote:

> If the intermediate object would be created at the right moment
>
>       _Tp __tmp(std::forward<_Args>(__args)...);
>       _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
>                        _GLIBCXX_MOVE(*(this->_M_impl._M_finish
>                                    - 1)));
>       ++this->_M_impl._M_finish;
>       _GLIBCXX_MOVE_BACKWARD3(__position.base(),
>                   this->_M_impl._M_finish - 2,
>                   this->_M_impl._M_finish - 1);
>       *__position = std::move(__tmp);

imposes no penalty at all (except in an exceptional case which nobody cares about).

We can have a slight overhead when operator= of the element type is defined as follows:

    struct X
    {
        X(X const &);
        X(X &&) noexcept;

        X &operator =(X x) noexcept
        {
            swap(x);
            return *this;
        }
        void swap(X &) noexcept;
        ....
    };

When parameter x is initialized by a prvalue of type X, a compiler is allowed to omit the move construction and create the temporary object directly in x regardless of whether the observable behavior would be changed (12.8/31).

When parameter x is initialized by an xvalue of type X, the same optimization can be done by a compiler only if it is able to prove that the observable behavior would not be changed.

BTW, the GNU C++ implementation of vector's emplace doesn't satisfy the following requirement in 23.3.6.5/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.
 
On Jul 8, 2012, at 5:10 PM, Nikolay Ivchenkov wrote:

> How soon will the nearest TC come?

I don't think anyone knows at this point.  The committee is talking about a 2017 standard (not just a TC).

I remember discussion at admin-1665.

>         std::vector<X> v2;
>         v2.emplace(v2.end(), X()); // doesn't compile
>     }

This doesn't:

error: call to 'swap' is ambiguous.  That error message looks correct to me.

AFAIK, vector's emplace doesn't require the element type to be Swappable.

Howard Hinnant

unread,
Jul 9, 2012, 10:33:19 AM7/9/12
to std-dis...@isocpp.org
Such an X may have much bigger performance problems, especially if it contains vector or string data members. I advise people not to do this, but it seems I'm fighting an uphill battle on that one.

>
> BTW, the GNU C++ implementation of vector's emplace doesn't satisfy the following requirement in 23.3.6.5/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.

Good point.

>
> On Jul 8, 2012, at 5:10 PM, Nikolay Ivchenkov wrote:
>
> > How soon will the nearest TC come?
>
> I don't think anyone knows at this point. The committee is talking about a 2017 standard (not just a TC).
>
> I remember discussion at admin-1665.
>
> > std::vector<X> v2;
> > v2.emplace(v2.end(), X()); // doesn't compile
> > }
>
> This doesn't:
>
> error: call to 'swap' is ambiguous. That error message looks correct to me.
>
> AFAIK, vector's emplace doesn't require the element type to be Swappable.

Upon closer study libc++'s error is an ambiguity when trying to swap X*, not X. If you change the global swap to:

void swap(X &, X &) = delete;

then it compiles. This is picked up during the parsing of a noexcept spec on an implementation detail of vector's storage facility (which is a crude version of tuple).

Howard

David Abrahams

unread,
Jul 9, 2012, 11:03:52 AM7/9/12
to std-dis...@isocpp.org

On Jul 9, 2012, at 10:33 AM, Howard Hinnant wrote:

> On Jul 9, 2012, at 4:08 AM, Nikolay Ivchenkov wrote:
>
>>
>> We can have a slight overhead when operator= of the element type is defined as follows:
>>
>> struct X
>> {
>> X(X const &);
>> X(X &&) noexcept;
>>
>> X &operator =(X x) noexcept
>> {
>> swap(x);
>> return *this;
>> }
>> void swap(X &) noexcept;
>> ....
>> };
>>
>> When parameter x is initialized by a prvalue of type X, a compiler is allowed to omit the move construction and create the temporary object directly in x regardless of whether the observable behavior would be changed (12.8/31).
>>
>> When parameter x is initialized by an xvalue of type X, the same optimization can be done by a compiler only if it is able to prove that the observable behavior would not be changed.
>
> Such an X may have much bigger performance problems, especially if it contains vector or string data members. I advise people not to do this, but it seems I'm fighting an uphill battle on that one.

I'm with you on this one, FWIW. I view this implementation of operator= as "a good first cut if you aren't concerned about speed." The reason to write it this way is that it's easy to get the right semantics (performance aside).

signature.asc
Reply all
Reply to author
Forward
0 new messages