std::vector using copy constructor on existing elements during reserve()

std:vector is using the element copy constructor on existing elements during its resizing operation in reserve(). I would expect it to use the move constructor for efficiency. Is this intended behavior?

This is with the libc++ delivered with Xcode 10.1.

Attached is a sample program displaying the behavior.

main.cpp (3 KB)

output.txt (524 Bytes)

std:vector is using the element copy constructor on existing elements during its resizing operation in reserve(). I would expect it to use the move constructor for efficiency. Is this intended behavior?

Yes, this behavior is mandated by the standard. The copy constructor is used when the move constructor is not noexcept. From cppreference’s std::move_if_noexcept documentation:

std::vector::resize, which may have to allocate new storage and then move or copy elements from old storage to new storage.
If an exception occurs during this operation, std::vector::resize undoes everything it did to this point, which is only possible if
std::move_if_noexcept was used to decide whether to use move construction or copy construction. (unless copy constructor is

not available, in which case move constructor is used either way and the strong exception guarantee may be waived)

https://en.cppreference.com/w/cpp/utility/move_if_noexcept

If you add noexcept to your move constructor, vector should call the move constructor.

I’m curious about how people feel this should/could behave with -fno-exceptions. IIRC, Libc++ calls the copy constructor (following the standard), but -fno-exceptions isn’t really standard, and from a user perspective that doesn’t make sense. noexcept is essentially meaningless with -fno-exceptions.

Thoughts?

Concretely, in LLVM, I committed r266909 and r267299 (use SmallVector instead of std::vector) because it didn’t make sense to litter an -fno-exceptions code base with noexcept.

I’m curious about how people feel this should/could behave with -fno-exceptions. IIRC, Libc++ calls the copy constructor (following the standard), but -fno-exceptions isn’t really standard, and from a user perspective that doesn’t make sense. noexcept is essentially meaningless with -fno-exceptions.

I personally feel like it would be surprising if -fno-exceptions caused the move constructor to be called instead of the copy constructor. My mental model is that -fno-exceptions only disables exceptions as a language feature, but doesn’t really instruct libraries to provide a different API (even though the difference in this case is very small). Instead, I feel like it would make more sense if “everything” was implicitly marked noexcept when -fno-exceptions is used (because that’s really the case). The library would behave optimally with -fno-exceptions without having to do anything special.

Louis

The only reason we call the copy constructor is to provide the strong exception safety guarantee (which arguably may have been a mistake). Without exceptions the guarantee is satisfied trivially.

I think we should move the objects. Doing the more efficient thing provides value to the user.

Google compiles a ton of code without exceptions. I will experiment with making move_if_noexcept always move and report back.

/Eric

I’m curious about how people feel this should/could behave with -fno-exceptions. IIRC, Libc++ calls the copy constructor (following the standard), but -fno-exceptions isn’t really standard, and from a user perspective that doesn’t make sense. noexcept is essentially meaningless with -fno-exceptions.

I personally feel like it would be surprising if -fno-exceptions caused the move constructor to be called instead of the copy constructor. My mental model is that -fno-exceptions only disables exceptions as a language feature, but doesn’t really instruct libraries to provide a different API (even though the difference in this case is very small). Instead, I feel like it would make more sense if “everything” was implicitly marked noexcept when -fno-exceptions is used (because that’s really the case). The library would behave optimally with -fno-exceptions without having to do anything special.

IIUC what you’re saying: the library should just use the noexcept operator to do its business, and it seem like -fno-exceptions should make noexcept always return true? That would be pretty consistent (more than teaching the library about -fno-exceptions for move / copy).

I’m curious about how people feel this should/could behave with -fno-exceptions. IIRC, Libc++ calls the copy constructor (following the standard), but -fno-exceptions isn’t really standard, and from a user perspective that doesn’t make sense. noexcept is essentially meaningless with -fno-exceptions.

I personally feel like it would be surprising if -fno-exceptions caused the move constructor to be called instead of the copy constructor. My mental model is that -fno-exceptions only disables exceptions as a language feature, but doesn’t really instruct libraries to provide a different API (even though the difference in this case is very small). Instead, I feel like it would make more sense if “everything” was implicitly marked noexcept when -fno-exceptions is used (because that’s really the case). The library would behave optimally with -fno-exceptions without having to do anything special.

IIUC what you’re saying: the library should just use the noexcept operator to do its business, and it seem like -fno-exceptions should make noexcept always return true? That would be pretty consistent (more than teaching the library about -fno-exceptions for move / copy).

Yes, that’s what I mean. I agree with Eric that moving instead of copying provides value, but I think the right way to do it is not to special case libc++, but instead to have all code bases become better under -fno-exceptions by making noexcept do the right thing.

Louis

I’m curious about how people feel this should/could behave with -fno-exceptions. IIRC, Libc++ calls the copy constructor (following the standard), but -fno-exceptions isn’t really standard, and from a user perspective that doesn’t make sense. noexcept is essentially meaningless with -fno-exceptions.

I personally feel like it would be surprising if -fno-exceptions caused the move constructor to be called instead of the copy constructor. My mental model is that -fno-exceptions only disables exceptions as a language feature, but doesn’t really instruct libraries to provide a different API (even though the difference in this case is very small). Instead, I feel like it would make more sense if “everything” was implicitly marked noexcept when -fno-exceptions is used (because that’s really the case). The library would behave optimally with -fno-exceptions without having to do anything special.

IIUC what you’re saying: the library should just use the noexcept operator to do its business, and it seem like -fno-exceptions should make noexcept always return true? That would be pretty consistent (more than teaching the library about -fno-exceptions for move / copy).

Yes, that’s what I mean. I agree with Eric that moving instead of copying provides value, but I think the right way to do it is not to special case libc++, but instead to have all code bases become better under -fno-exceptions by making noexcept do the right thing.

Yeah, that makes sense to me too.

+Richard, I could have sworn you and I chatted quickly about this back in 2016 by my mail-search-fu is failing me. What are your thoughts on having -fno-exceptions affect the value of noexcept?

Changing the behavior of unary noexcept is a much larger and complicated change than fixing vector.

At the moment -fno-exceptions is a subset of standard C++. Changing noexcept would make it non-standard and non-portable.
Do we want that?

And there’s just nastiness like this:

void bar();
void bar() noexcept(noexcept(bar())); // Cool
void bar(); noexcept(false); // Also Cool

We should pursue std::vector separately. I think the current wording allows either moves or copies, as long as “if an exception is thrown, there are no effects”.
For example [1]

void push_back(const T& x); void push_back(T&& x);

Remarks: […]
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 while inserting a single element at the end and T is Cpp17CopyInsertable or is_­nothrow_­move_­constructible_­v is true, there are no effects.

Otherwise, if an exception is thrown by the move constructor of a non-Cpp17CopyInsertable T, the effects are unspecified.

Cpp17CopyInsertible implies Cpp17MoveInsertable. And we meet the “if exception, no effects” requirements trivially.
I might be squinting at the wording too hard, but I think I can fix this as QoI.

/Eric

[1] http://eel.is/c++draft/container.requirements#general-11
[2] http://eel.is/c++draft/sequences#vector.modifiers

I'm curious about how people feel this should/could behave with -fno-exceptions. IIRC, Libc++ calls the copy constructor (following the standard), but -fno-exceptions isn't really standard, and from a user perspective that doesn't make sense. `noexcept` is essentially meaningless with -fno-exceptions.

I personally feel like it would be surprising if -fno-exceptions caused the move constructor to be called instead of the copy constructor. My mental model is that -fno-exceptions only disables exceptions as a language feature, but doesn't really instruct libraries to provide a different API (even though the difference in this case is very small). Instead, I feel like it would make more sense if "everything" was implicitly marked noexcept when -fno-exceptions is used (because that's really the case). The library would behave optimally with -fno-exceptions without having to do anything special.

IIUC what you’re saying: the library should just use the noexcept operator to do its business, and it seem like -fno-exceptions should make noexcept always return true? That would be pretty consistent (more than teaching the library about -fno-exceptions for move / copy).

Yes, that's what I mean. I agree with Eric that moving instead of copying provides value, but I think the right way to do it is not to special case libc++, but instead to have all code bases become better under -fno-exceptions by making noexcept do the right thing.

Yeah, that makes sense to me too.

+Richard, I could have sworn you and I chatted quickly about this back in 2016 by my mail-search-fu is failing me. What are your thoughts on having -fno-exceptions affect the value of `noexcept`?

Here are some questions I'd want to have answers to:
* How does this impact GCC compatibility?
* How does this impact the size and performance of generated code?
* Would an exception result in a guaranteed call to terminate like in
a real noexcept function, or undefined behavior as in today's
-fno-exceptions?
* What's the impact on C++17 code, now that 'noexcept' is part of the
type system? Given
    void f();
    ... is that a function with a non-noexcept function type, with
noexcept(f()) being true, or is the type of f also adjusted to 'void()
noexcept'? Both answers seem troublesome.
* What migration strategy do we provide or encourage, in order to
help people move away from the -fno-exceptions language dialect and
back to standard C++? How does this change enable / hinder that?

Longer-term, "Clang should drive the standard, not diverge from it"
(Clang - Get Involved). We should be thinking about
the long term direction of exceptions in C++, and in particular Herb's
P0709. In that vein (particularly considering P0709R2's footnote 10
and nearby text) I'm wondering if we should have an experimental
option to specify that functions are noexcept by default (overridable
by an explicit exception specification) as a bridge for
-fno-exceptions users to take back towards standard C++.

Regardless of whatever else we do, I think we should work to make use
of noexcept a viable alternative to today's -fno-exceptions. In
particular, that means fixing the longstanding issue that LLVM can't
model C++'s "you may choose to not unwind if unwinding would reach a
noexcept function" rule, which currently results in bad codegen for
noexcept functions compared to -fno-exceptions. Eg, compare clang and
GCC codegen with and without -fno-exceptions for:

struct X { ~X(); };
void g();
void f() noexcept {
    X x;
    g();
}

Changing the behavior of unary noexcept is a much larger and complicated change than fixing vector.

At the moment `-fno-exceptions` is a subset of standard C++. Changing noexcept would make it non-standard and non-portable.
Do we want that?

If move_if_noexcept or vector::resize switches to a more efficient
algorithm under -fno-exceptions, that creates the same kind of
surprises that we'd see if functions became noexcept(true) by default
under -fno-exceptions; either way, code does not have portable
behavior or performance between implementations / implementation
flags, so I think it's a difference in degree rather than in kind.
(People will write code that relies on their copy constructor not
being called when the vector resizes, and would be surprised if they
get different behavior and performance when (for instance) they turn
on -fexceptions so they can use a third-party library that requires
it.)

So I think the concerns here really apply to all of our options,
although to differing extents.

If you want to modify libc++'s vector and friends to
move-even-if-not-noexcept when exceptions are disabled, that's up to
you and your fellow libc++ maintainers; I agree that it appears to be
a conforming implementation strategy, but it's a complex decision due
to the above concerns.

Changing the behavior of unary noexcept is a much larger and complicated change than fixing vector.

At the moment -fno-exceptions is a subset of standard C++.

I disagree with this assertion. -fno-exceptions has nothing to do with standard C++.

Changing noexcept would make it non-standard and non-portable.
Do we want that?

We’re already non-standard and non-portable here.

– Marshall

This direction makes sense to me.

Is anyone going to take point on moving the Clang changes forward?

If not, I plan to propose a target fix for vector in the meantime.

Is anyone going to take point on moving the Clang changes forward?

If not, I plan to propose a target fix for vector in the meantime.

Reading Richard’s email above, it didn’t seem obvious to me at all that we wanted to do this at all. Did I get that wrong?

Louis

Is anyone going to take point on moving the Clang changes forward?

If not, I plan to propose a target fix for vector in the meantime.

Reading Richard’s email above, it didn’t seem obvious to me at all that we wanted to do this at all. Did I get that wrong?

Right; I floated this idea, but Richard convinced me it’s a bad one.