When does a C++ compiler infer noexcept for a method? - c++

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.

Related

Move assignment operator and exceptions

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.

std::packaged_task should have deleted copy c'tor with const parameter

Link https://cplusplus.github.io/LWG/issue2067 provides the following discussion:
Class template packaged_task is a move-only type with the following form of the deleted copy operations:
packaged_task(packaged_task&) = delete;
packaged_task& operator=(packaged_task&) = delete;
Note that the argument types are non-const. This does not look like a typo to me, this form seems to exist from the very first proposing paper on N2276. Using either of form of the copy-constructor did not make much difference before the introduction of defaulted special member functions, but it makes now an observable difference. This was brought to my attention by a question on a German C++ newsgroup where the question was raised why the following code does not compile on a recent gcc:
#include <utility>
#include <future>
#include <iostream>
#include <thread>
int main() {
std::packaged_task<void()> someTask([]{ std::cout << std::this_thread::get_id() << std::endl; });
std::thread someThread(std::move(someTask)); // Error here
// Remainder omitted
}
It turned out that the error was produced by the instantiation of some return type of std::bind which used a defaulted copy-constructor, which leads to a const declaration conflict with [class.copy] p8.
Some aspects of this problem are possibly core-language related, but I consider it more than a service to programmers, if the library would declare the usual form of the copy operations (i.e. those with const first parameter type) as deleted for packaged_task to prevent such problems.
Could anybody explain the meaning of the marked statement? I don't undestand how the missing const qualifer affects the compilation process, and how this behavior is explained in standard.
What is the point of adding const to the parameter of the deleted copy constructor?
Here is a toy example:
struct problem {
problem()=default;
problem(problem&&)=default;
problem(problem&)=delete;
};
template<class T>
struct bob {
T t;
bob()=default;
bob(bob&&)=default;
bob(bob const&)=default;
};
int main() {
problem p;
problem p2 = std::move(p);
bob<problem> b;
bob<problem> b2 = std::move(b);
}
bob<problem> fails to compile because the bob(bob const&)=default errors out when it interacts with problem(problem&)=delete.
Arguably the standard "should" error-out cleanly when it determines that it cannot implement bob(bob const&), and treat the =default as =delete (like it would if we had problem(problem const&)=delete), but the standard wording isn't going to be flawless in this corner case of a corner case. And this corner of a corner case is going to be strange and quirky enough that I'm not certain a general rule that makes it translate =default to =delete would be right!
The fix if we problem(problem const&)=delete (well, to packaged_task) is going to be so much cleaner than anything we do to =default ctor rules.
Now standard delving:
First, it is obvious that the implicitly declared copy constructor of bob<problem> above is going to have signature bob(bob&) in [class.ctor]. I won't even quote the standard for that, because lazy.
We go and explicitly default bob(bob const&) copy ctor, which differs in signature from the one that would be implicitly declared.
There are rules about explicitly defaulting functions and their conflict with the signatures is in 11.4.2.
In Explicitly-defaulted functions[dcl.fct.def.default] 11.4.2/2
2 The typeT1of an explicitly defaulted function F is allowed to differ from the type T2 it would have had if it were implicitly declared, as follows:
—(2.1) T1 and T2 may have differing ref-qualifiers; and
—(2.2) if T2 has a parameter of type const C&, the corresponding parameter of T1 may be of type C&.
If T1 differs from T2 in any other way, then:
—(2.3) if F is an assignment operator, and the return type of T1 differs from the return type of T2 or T1’s parameter type is not a reference, the program is ill-formed;
—(2.4) otherwise, if F is explicitly defaulted on its first declaration, it is defined as deleted;
—(2.5) otherwise, the program is ill-formed.
The defaulted one is T1, which contains const& not &, so (2.2) doesn't apply.
My reading actually has it getting caught on (2.4); the type of bob(bob const&) differs from the implicitly declared bob(bob&) in an impermissible way; but first declaration is defaulted, so it should be deleted.
I'm looking at the n4713 draft version; maybe an older version didn't have that clause.

disabled exceptions and noexcept()

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.

Can a compiler automatically move a function argument if the function call is the return statement?

In the following situation can a compiler automatically move the function argument v or does it have to be declared manually?
std::vector Filter(std::vector v);
void DoSomeStuffAndCallFilter(std::vector v)
{
// do some stuff to v
// can the compiler automatically std::move v in this call?
// ie. return Filter(std::move(v));
//
return Filter(v);
}
In your case, the compiler can do so as an allowed optimisation under the as-if rule, because it knows the destructor and copy-constructor of your std::vector intimately, and can thus prove there is no difference to the observable behavior.
Still, it is a "quality of implementation issue", and depends on heavy optimisations being done.

What is the difference between C++03 `throw()` specifier and C++11 `noexcept`?

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.