Reputation: 2607
Trying to design some exception-free classes, I have an inheritance structure similar to this, but I have found the noexcept specifier to be of little to no help when working with member functions as the specifier is not scoped as being within the function.
class Base
{
protected:
Base() noexcept {}
};
class Derived : public Base
{
public:
// error: 'Base::Base()' is protected
Derived() noexcept(noexcept(Base{})) : Base{} {}
// error: 'foo' was not declared in this scope
Derived(int) noexcept(noexcept(foo())) {}
// error: invalid use of 'this' at top level
Derived(float) noexcept(noexcept(this->foo())) {}
void foo() noexcept {}
};
Is this perhaps something that is being improved in C++17? Trying to search for this has yielded no relevant results. For now I've resigned to some very ugly (and possibly incorrect) attempts such as noexcept(noexcept(static_cast<Derived*>(nullptr)->foo()))
, but this doesn't assist in the case of the base class constructor, which is protected.
Is it even currently possible to declare a noexcept specifier which references a protected base class method like this? noexcept(auto) might be relevant, but of course isn't possible yet. Have I overlooked anything else that would allow me to include this specifier, or do I simply have to omit it in that case?
Upvotes: 6
Views: 528
Reputation: 213648
This is really multiple questions in one.
this
...From my understanding, the use of this
is supposed to be completely superfluous but compiler support for C++11 is not entirely universal. This should work, according to the C++11 standard:
struct Base {
void func() noexcept;
};
struct Derived() {
void func() noexcept(noexcept(Base::func())) {}
};
Note that base_func()
is a non-static member function, but because it appears in an "unevaluated operand" it is okay. From n3337 sec 4.1.1:
An id-expression that denotes a non-static data member or non-static member function of a class can only be used:
...
- if that id-expression denotes a non-static data member and it appears in an unevaluated operand.
However, some compilers do not support this. You are then forced to use std::declval
:
#include <utility>
struct Base {
void func() noexcept;
};
struct Derived() {
void func() noexcept(noexcept(std::declval<Base>().func())) {}
};
I read through the relevant parts of the standard about "unevaluated operands" and "member access control" and I have come to the conclusion that the standard is a bit ambiguous. It mentions that a protected
name can be used only by members, friends, and derived classes. The question is whether unevaluated operands "use" the member names which appear in them. They certainly don't odr-use the member names, and can even use member names if no definition is provided, and this kind of ambiguity is exactly why the term "odr-use" even exists! For example,
int f(); // No definition anywhere in program
int x = sizeof(f()); // Completely valid, even without definition of f
struct X {
X() = delete; // Cannot use this constructor
};
int xsize = sizeof(X{}); // Completely valid
Even though it is somewhat unclear, I have a hard time imagining that the C++ committee could have intended to let you use deleted member functions in unevaluated operands but not inaccessible member functions. However, I am not certain.
Note that the above code compiles without error both with GCC and Clang. However, the following code is does not:
class X {
X(){}
};
class Y {
Y() = delete;
};
bool xokay = noexcept(X{}); // Error!
bool yokay = noexcept(Y{}); // Ok
Both GCC and Clang accept Y but not X, which seems a bit weird to say the least. The following code is accepted by Clang but not GCC, and using std::declval
does not help:
class Base {
protected:
void func();
};
class Derived : public Base {
// Clang accepts this, GCC does not.
void func2() noexcept(noexcept(Base::func())) {}
};
What a mess.
The conclusion here is that it seems that there is plenty of inconsistency to go around, and plenty of gaps between current compilers and the C++11 specs.
Upvotes: 2
Reputation: 4034
You can work around it by using an expression where the Base constructor is in scope like this:
struct test_base : public Base {};
Derived() noexcept(noexcept(test_base())) : Base() {}
I believe the reason you cannot use Base()
directly is related to this question.
The way protected access specifier works, it allows the derived class B to access the contents of an object of base class A only when that object of class A is a subobject of class B. That means that the only thing you can do in your code is to access the contents of A through B: you can access the members of A through a pointer of type B * (or a reference of type B &). But you cannot access the same members through a pointer of type A * (or reference A &).
It's the same as if you had a member function like this:
void badfunc()
{
B b;
}
You're trying to use Base
's constructor directly instead of going through Derived
. When you initialize the base in the constructor initialization list, that is a special context that allows you to call the constructor because you're doing it as part of initializing Derived
.
Upvotes: 2