I encounter an issue while using the noexcept
specifier on derived classes, more precisely when the parent is an abstract class (has protected
constructors).
Hereafter is an example of the way I declare my classes.
public
constructor in the base class: Everything is ok.protected
and the derived class is no more "nothrow movable".Do I miss something? Is std::is_nothrow_move_constructible
the correct traits to use in derived class declarations or should I use something else?
#include <cstdlib>
#include <iostream>
class BaseOk
{
public:
BaseOk ( BaseOk&& other ) noexcept {}
};
class BaseNok
{
protected:
BaseNok ( BaseNok&& other ) noexcept {}
};
class ChildOk : public BaseOk
{
public:
ChildOk ( ChildOk&& other ) noexcept ( std::is_nothrow_move_constructible < BaseOk >::value )
: BaseOk ( std::move ( other ) ) {}
};
class ChildNok : public BaseNok
{
public:
ChildNok ( ChildNok&& other ) noexcept ( std::is_nothrow_move_constructible < BaseNok >::value )
: BaseNok ( std::move ( other ) ) {}
};
int main ()
{
std::cout << std::boolalpha;
std::cout << "Is BaseOk move constructible? " << std::is_move_constructible < BaseOk >::value << '\n';
std::cout << "Is ChildOk move constructible? " << std::is_move_constructible < ChildOk >::value << '\n';
std::cout << '\n';
std::cout << "Is BaseOk nothrow move constructible? " << std::is_nothrow_move_constructible < BaseOk >::value << '\n';
std::cout << "Is ChildOk nothrow move constructible? " << std::is_nothrow_move_constructible < ChildOk >::value << '\n';
std::cout << '\n';
std::cout << "Is BaseNok move constructible? " << std::is_move_constructible < BaseNok >::value << '\n';
std::cout << "Is ChildNok move constructible? " << std::is_move_constructible < ChildNok >::value << '\n';
std::cout << '\n';
std::cout << "Is BaseNok nothrow move constructible? " << std::is_nothrow_move_constructible < BaseNok >::value << '\n';
std::cout << "Is ChildNok nothrow move constructible? " << std::is_nothrow_move_constructible < ChildNok >::value << '\n';
std::cout << std::endl;
return EXIT_SUCCESS;
}
Output:
Is BaseOk move constructible? true
Is ChildOk move constructible? true
Is BaseOk nothrow move constructible? true
Is ChildOk nothrow move constructible? true
Is BaseNok move constructible? false
Is ChildNok move constructible? true
Is BaseNok nothrow move constructible? false
Is ChildNok nothrow move constructible? false
___ EDIT ____________________________________________________________
After searching around for a while, and regarding to the andswer of Oleg Bogdanov, it unfortunately seems not possible to combine protected
constructors with usage of noexcept ( is_nothrow_... )
.
I was writting abstract classes and declared constructors protected
for documentation purposes only. Now, constructors are back to public
but I'm facing another problem:
As an abstract class cannot be instanciated, std::is_nothrow_move_constructible<BaseClass>
returns false
and all derived classes can never be tagged as not throwing exceptions even if they are not.
See example below:
#include <cstdlib>
#include <iostream>
class Foo
{
public:
Foo ( Foo&& other ) noexcept {}
virtual ~Foo () = 0; // Removing '= 0' makes both outputs print 'true'.
};
Foo::~Foo () {}
class Bar : public Foo
{
public:
Bar ( Bar&& other ) noexcept ( std::is_nothrow_move_constructible < Foo >::value )
: Foo ( std::move ( other ) ) {}
};
int main ()
{
std::cout << std::boolalpha;
std::cout << "Foo: " << std::is_nothrow_move_constructible < Foo >::value << '\n';
std::cout << "Bar: " << std::is_nothrow_move_constructible < Bar >::value << '\n';
return EXIT_SUCCESS;
}
Output:
Foo: false
Bar: false
When evaluating to true
is_move_constructible works exactly like is_constructible, which in turn says that
T is an object or reference type and the variable definition T obj(std::declval()...); is well-formed
My guess is that in your case, definition BaseNok obj(...)
is not really well-formed, because you neither have public default ctor (its implicitly removed) nor any other accessible ctor (protected is not), thus it evals to false
. (The definition of well-formeness itself is arguable though)
ChildNok is still move_constructible because you made its move ctor public, other cases eval to false
exactly because std::is_move_constructible < BaseNok >::value
is already false
Edit: As for edited question, note section of is_constructible mentions
In many implementations, is_nothrow_constructible also checks if the destructor throws because it is effectively noexcept(T(arg))
When you keep your destructor pure virtual, it probably fails the check.
I am personally not sure if it's oversight or by-design of type traits, some issues are covered in LWG issue 2116
I'm not proposing a scalable solution here, but why would not you mark your derived classes unconditionally noexcept for now, given that base is noexcept() too