I have a couple of questions about move assignment operator and possibly thrown exceptions:
Provided that is_nothrow_move_assignable<T>::value is true for the type T, is it enough to conclude that move assignment operator for that type will never throw an exception? In other words, is it ok to have this noexcept specification:
struct MyClass
{
MyClass& operator = (MyClass&& other) noexcept(std::is_nothrow_move_assignable<OtherClass>::value)
{
this->data = std::move(other.data);
return *this;
}
OtherClass data;
};
(Let's assume that we cannot rely on defaulted move-assignment operator)
Talking specifically about std::vector, in C++11 its move-assignment operator may throw implementation-defined exceptions (according to the docs). And even since C++17 its noexcept specification still depends on the allocator traits. But this is a general case. Let's say we have two instrances of std::vector<double> with default allocator. Is it still possible that move assignment throws an exception in this case?
std::vector<double> v1{ 1,2,3 }, v2{4,5};
v1 = std::move(v2); // can it throw?
No.noexcept keyword says only, that it does not suppose to throw any exception and compiler will assume that the function will not throw exception, thus it will not produce exception-handing code. If noexcept function will throw an exception, the program will be terminated immediately. Thus said, I believe, using noexcept(std::is_nothrow_move_assignable<OtherClass>::value) is enough.
Lets take a look at the standard and cppreference:
https://en.cppreference.com/w/cpp/container/vector/operator%3D :
noexcept(std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value
|| std::allocator_traits<Allocator>::is_always_equal::value)
std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value, standard says([allocator.requirements])
If X::propagate_on_-
container_move_assignment::value is true, X shall satisfy the MoveAssignable requirement and the move operation shall not throw exceptions.
the double type is plain old builtin type, so it satisfies MoveAssignable requirements (https://en.cppreference.com/w/cpp/named_req/MoveAssignable) and thus exception should not be thrown.
std::allocator_traits<Allocator>::is_always_equal::value is true
In noexcept statement binary OR is used, so it shall not throw any exceptions.
Related
The question is quite simple.
This is the declaration of templated operator= for std::any:
template<typename ValueType>
any& operator=( ValueType&& rhs );
I would expect it to be:
template<typename ValueType>
any& operator=( ValueType&& rhs ) noexcept(noexcept(std::declval<std::any>() = std::forward<ValueType>(std::declval<ValueType>()));
Namely, if you can copy-assign ValueType to any in a noexcept fashion, then you should be able to have noexcept.
Maybe I am missing something.
The literal answer is that such a specification would be recursive (you're saying the assignment should be noexcept if the assignment is noexcept).
But the probably more useful answer is that since any may have to allocate, you could only really have noexcept assignment in the case that decay_t<ValueType> is
sufficiently small (so as to not need allocation), and
nothrow move constructible, and
nothrow constructible from ValueType
The only way to specify the noexcept condition would require you to also specify what "sufficiently small" means - which would limit implementation freedom, for questionable gain.
The standard library doesn't typically use conditional noexcept - so why would this be the... exception?
template<class T> any& operator=(T&& rhs);
[any.assign]/12 - Throws: Any exception thrown by the selected constructor of VT.
The overload you showed only participate in overload resolution if ValueType is copy constructible, which leave the door open for the copy construction of ValueType to throw during the std::any assignment.
Note that you're also showing a noexcept specification defined in terms of the same operation being defined.
I just noticing a std::vector<Foo> of mine was copying instead of moving its elements when resizing - even though Foo has a move ctor:
class Foo {
// ...
Foo(Foo&& other) : id_(other.id_), ptr_(other.ptr_), flag(other.flag)
{
other.flag = false;
};
// ...
int id_;
void* ptr_;
bool flag;
}
Then I read:
Resize on std::vector does not call move constructor
which reminded me that std::vector will only use move construction if the elements' move ctor is declared noexcept. When I add noexcept, move ctor's are called.
My question is: Why, given the move ctor's code, does the compiler not determine it to be noexcept? I mean, it can know for a fact that an exception cannot be thrown. Also, is inferring noexcept disallowed by the standard, or not just done by my specific compiler?
I'm using GCC 5.4.0 on GNU/Linux.
tl;dr: The compiler is not allowed to infer noexcept
Why, given the move ctor's code, does the compiler not determine it to be noexcept?
Because noexcept specification is determined based on the declaration - not the definition. This is analogous to how const specification works. The compiler isn't allowed to determine a function to be const even though its implementation doesn't modify any members.
is inferring noexcept disallowed by the standard
As I understand, yes:
[except.spec] ... absence of an exception-specification in a
function declarator other than that for a destructor (12.4) or a deallocation function (3.7.4.2) denotes an
exception specification that is the set of all types.
Inferring something other than set of all types would contradict this rule. Of course, when the compiler can prove that no exception can be thrown, it can optimise away any stack unwinding code under the as-if rule, but such optimisations cannot affect SFINAE introspection.
There has been discussion about possibility of introducing noexcept(auto), which would be an explicit way of letting the compiler infer noexcept specification.
I would like to use the optional idiom inside my constexpr function to easily clarify if the variable is set or not.
What I have tried with std::experimental::optional:
constexpr bool call()
{
std::experimental::optional<bool> r;
r = true; // Error
// Similar error with:
// r = std::experimental::optional<bool>(true);
if (!r)
{
return false;
}
return *r;
}
I get the error: call to non-constexpr function - so the assignment is not possible, because this operation cannot be constexpr (Example).
But if I implement my own (very ugly, just for example) optional class, it works, because I don´t implement the assignment operator/constructor explicit.
template<typename T>
struct optional
{
bool m_Set;
T m_Data;
constexpr optional() :
m_Set(false), m_Data{}
{
}
constexpr optional(T p_Data) :
m_Set(true), m_Data(p_Data)
{
}
explicit constexpr operator bool()
{
return m_Set;
}
constexpr T operator *()
{
return m_Data;
}
};
How could I use std::..::optional in the same context with assignment inside constexpr functions?
Basically, you can't. The problem with your simple implementation is that it requires T be default-constructible - if this is not the case, this won't work.
To get around this, most implementation use either a union or some (suitably aligned) storage that can hold a T. If you are passed a T in the constructor, then all well and good, you can initialize this directly (hence it will be constexpr). However, the tradeoff here is that when calling operator=, copying the value across may require a placement-new call, which cannot be constexpr.
For example, from LLVM:
template <class _Up,
class = typename enable_if
<
is_same<typename remove_reference<_Up>::type, value_type>::value &&
is_constructible<value_type, _Up>::value &&
is_assignable<value_type&, _Up>::value
>::type
>
_LIBCPP_INLINE_VISIBILITY
optional&
operator=(_Up&& __v)
{
if (this->__engaged_)
this->__val_ = _VSTD::forward<_Up>(__v);
else
{
// Problem line is below - not engaged -> need to call
// placement new with the value passed in.
::new(_VSTD::addressof(this->__val_)) value_type(_VSTD::forward<_Up>(__v));
this->__engaged_ = true;
}
return *this;
}
As for why placement new is not constexpr, see here.
This is not possible, as explained in n3527:
Making optional a literal type
We propose that optional<T> be a literal type for trivially
destructible T's.
constexpr optional<int> oi{5};
static_assert(oi, ""); // ok
static_assert(oi != nullopt, ""); // ok
static_assert(oi == oi, ""); // ok
int array[*oi]; // ok: array of size 5
Making optional<T> a literal-type in general is impossible: the
destructor cannot be trivial because it has to execute an operation
that can be conceptually described as:
~optional() {
if (is_engaged()) destroy_contained_value();
}
It is still possible to make the destructor trivial for T's which
provide a trivial destructor themselves, and we know an efficient
implementation of such optional<T> with compile-time interface —
except for copy constructor and move constructor — is possible.
Therefore we propose that for trivially destructible T's all
optional<T>'s constructors, except for move and copy constructors,
as well as observer functions are constexpr. The sketch of reference
implementation is provided in this proposal.
In other words, it's not possible to assign a value to r even if you mark it as constexpr. You must initialize it in the same line.
How could I use std::..::optional in the same context with assignment
inside constexpr functions?
std::optional is meant to hold a value that may or may not be present. The problem with std::optional's assignment is that it must destroy the old state (call the destructor of the contained object) if any. And you cannot have a constexpr destructor.
Of cause, Trivial and integral types shouldn't have a problem, but I presume the generalization was to keep things sane. However, Assignment could have been made constexpr for trivial types. Hopefully, it will be corrected. Before then, you can role out yours. :-)
Even std::optional's constructor that you think is constexpr, is actually selectively constexpr (depending on whether the selected object constructor is). Its proposal can be found here
Unfortunately, constexpr support in std::optional is somewhat rudimentary; the constexpr-enabled member functions are just the (empty and engaged) constructors, the destructor and some observers, so you cannot alter the engaged state of an optional.
This is because there would be no way to implement assignment for non-trivially copyable types without using placement new and in-place destruction of the contained object, which is illegal within constexpr context. The same currently holds for copy and move constructors, although that may change with guaranteed copy elision, but in any case the standard marks those special member functions as non-constexpr, so you cannot use them in constexpr context.
The fix would be to make the assignment operator conditionally constexpr dependent on whether the contained type is trivial (std::is_trivial_v<T>).
There is some discussion of this issue at the reference implementation; although it's probably too late to get constexpr assignment for trivial optionals into the next version of the Standard, there's nothing preventing you writing your own (e.g. by copying and fixing the reference implementation).
std::swap is declared this way:
template <class T> void swap (T& a, T& b)
noexcept (is_nothrow_move_constructible<T>::value &&
is_nothrow_move_assignable<T>::value);
If I disable exceptions in my program (like with -fno-exceptions for g++) will std::swap use move operations for my custom types if they are move-enabled no matter if they are noexcept or not?
EDIT: follow-up question:
After realizing that std::swap will always use moves if my type has them, my real question is what happens to traits like is_nothrow_move_assignable<>?
Will std::vector always use moves when reallocating if my types have noexcept(true) move operations?
The noexcept-specification on swap solely tells the user where she can use swap without encountering an exception. The implementation is practically always equivalent to
auto tmp = std::move(a);
a = std::move(b);
b = std::move(tmp);
Which moves the objects around if, and only if, overload resolution selects a move assignment operator and/or constructor.
Yes. noexcept simply specifies that std::swap will not throw if T's move constructor and move assignment will not throw. It does not affect the behavior of the body of swap whatsoever - which will use T's move constructor and move assignment regardless of whether or not they throw and regardless of whether or not you compile with exceptions enabled.
Is there any difference between throw() and noexcept other than being checked at runtime and compile time respectively?
This Wikipedia C++11 article suggests that the C++03 throw specifiers are deprecated.
Why so ... Is the noexcept capable enough to cover all that at compile time?
Note: I checked this question and this article, but couldn't determine the solid reason for its deprecation.
Exception specifiers were deprecated because exception specifiers are generally a terrible idea. noexcept was added because it's the one reasonably useful use of an exception specifier: knowing when a function won't throw an exception. Thus it becomes a binary choice: functions that will throw and functions that won't throw.
noexcept was added rather than just removing all throw specifiers other than throw() because noexcept is more powerful. noexcept can have a parameter which compile-time resolves into a boolean. If the boolean is true, then the noexcept sticks. If the boolean is false, then the noexcept doesn't stick and the function may throw.
Thus, you can do something like this:
struct<typename T>
{
void CreateOtherClass() { T t{}; }
};
Does CreateOtherClass throw exceptions? It might, if T's default constructor can. How do we tell? Like this:
struct<typename T>
{
void CreateOtherClass() noexcept(is_nothrow_default_constructible<T>::value) { T t{}; }
};
Thus, CreateOtherClass() will throw iff the given type's default constructor throws. This fixes one of the major problems with exception specifiers: their inability to propagate up the call stack.
You can't do this with throw().
noexcept isn't checked at compile time.
An implementation shall not reject an expression merely because when executed it throws or might throw an exception that the containing function does not allow.
When a function that is declared noexcept or throw() attempts to throw an exception the only difference is that one calls terminate and the othe calls unexpected and the latter style of exception handling has effectively been deprecated.
std::unexpected() is called by the C++ runtime when a dynamic exception specification is violated: an exception is thrown from a function whose exception specification forbids exceptions of this type.
std::unexpected() may also be called directly from the program.
In either case, std::unexpected calls the currently installed std::unexpected_handler. The default std::unexpected_handler calls std::terminate.