geza
geza

Reputation: 29970

Can std::vector<T> use T's move constructor, if exceptions are disabled?

When std::vector<T> is out of storage at emplace_back(), it uses copy constructor to copy elements into the new storage, if T's move constructor is not noexcept. This program prints copy! (godbolt):

#include <vector>
#include <cstdio>

struct T {
    T() = default;
    T(T &&) { printf("move!\n"); }
    T(const T &) { printf("copy!\n"); }
};

int main() {
    std::vector<T> v;
    v.emplace_back();
    v.emplace_back();
}

If I mark move constructor with noexcept, this program prints move!. All this is expected.

Now, as I don't write code, which uses exceptions, I turn off exceptions with -fno-exceptions. And I expected, that my example (without noexcept) will print move!. But, both GCC and clang still print copy!.

Is this behavior mandated by the standard? Or is the compiler allowed to use move, it is just not optimized for this case?

Upvotes: 3

Views: 441

Answers (3)

ComicSansMS
ComicSansMS

Reputation: 54669

The standard does not say anything about what happens when you turn off exceptions. ISO C++ is a single language with no variants, by deactivating exceptions you switch to a dialect that is no longer covered by the standard.

An implementation could choose to enable what you ask for, but doing so would require additional effort by the library implementation. For a standard conformant implementation, the decision of whether to move or copy will be based on std::is_­nothrow_­move_­constructible, which is specified to only base that decision on the noexcept specifier in the move constructor's signature and not on whether or not the constructor actually throws an exception. To achieve what you desire, the implementation would have to implement a different detection mechanism for the -fno-exceptions dialect as a non-standard extension.

The relevant paragraph in the standard is [vector.modifiers]:

template<class... Args> constexpr reference emplace_back(Args&&... args);

[...] If an exception is thrown while inserting a single element at the end and T is Cpp17CopyInsertable or is_­nothrow_­move_­constructible_­v<T> is true, there are no effects.

Even though it may not be obvious, this enforces calling the copy constructor when is_­nothrow_­move_­constructible_­v is false, as that is the only way for an implementation to comply with this requirement. Note that in earlier versions of the standard, things are even more subtle. The C++11 draft only says:

If an exception is thrown by the moveconstructor of a non-CopyInsertable T, the effects are unspecified.

Which de facto enforces the same behavior as the newer wording.

Now, speaking theoretically, if an implementation had additional means to detect whether a function can throw besides looking for the noexcept (e.g. through static analysis of the move constructor's implementation) would it be allowed to still move under the C++11 wording? The answer is yes, but I am not aware of any implementation that went through such considerable effort to enable a rather benign optimization like this.

Upvotes: 3

j6t
j6t

Reputation: 13507

To answer your verbatim question:

The Standard mandates that there be exceptions. If you disallow your compiler to use exceptions, the compiler operates outside of the Standard and the Standard has nothing to say about the behavior of your program.

That said, the move_if_noexcept condition that std::vector inquires is not asking whether the move constructor is able to throw exceptions in the compiled program, but whether you declared the move constructor noexcept. Therefore, the observed behavior does not change whether or not you compile with -fno-exceptions.


Edit regarding your question in the comment:

if move constructor is not noexcept, but the compiler can prove that the move constructor doesn't throw, is it allowed to use move constructor?

The compiler actually never comes to a point where it has the freedom to choose between the copy and the move constructor, not even if it could prove some suitable property by introspection.

So, the answer is no.

Somewhere in the code of emplace_back we will see something along the lines of

*dst = move_if_noexcept(*src);

Now, move_if_noexcept is specified as "use the move constructor if it is declared noexcept", but not specified as "use the move constructor if it does not throw an exception".

emplace_back has to be implemented in this generic way with move_if_noexcept to conform to the Standard for all possible value types. Of course, under -fno-exceptions, the compiler vendor could choose a different implementation, but as your experiments show, it doesn't.

Upvotes: 0

Aykhan Hagverdili
Aykhan Hagverdili

Reputation: 29985

Vector calls move_if_noexcept, which is designed to move if move copy constructor is defined as noexcept. Since you don't have noexcept, it's formally not noexcept, even if you never throw. Thus, it's not allowed to perform a move.

Considering you can mark all functions noexcept because you don't care about exceptions, I don't understand your concern.

Upvotes: 0

Related Questions