Why is the noexcept specifier not specified in the declared method?

Trying to create a few class-exclusive classes, I have an inheritance structure like this, but I found that the noexcept specifier has no help when working with member functions as a specifier is not part of 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 {} }; 

Demo

Perhaps this is something that is improving in C ++ 17? Attempting to find this did not yield any relevant results. At the moment, I have come to terms with some very ugly (and possibly incorrect) attempts, such as noexcept(noexcept(static_cast<Derived*>(nullptr)->foo())) , but this does not help with the protected base class constructor .

Is it even possible to declare a noexcept specifier that refers to a protected base class method? noexcept (auto) may be relevant, but, of course, is not yet possible. Am I missing anything else that would allow me to include this qualifier, or should I just omit it in this case?

+5
source share
2 answers

You can get around this using an expression in which the Base constructor is in an area like this:

 struct test_base : public Base {}; Derived() noexcept(noexcept(test_base())) : Base() {} 

I believe that the reason you cannot use Base() is directly related to this issue .

The method of protecting secure access works, it allows you to get a derived class B to access the contents of an object of base class A only when the object of class A is a subobject of class B. This means that the only thing you can do in your code is this is accessing the contents of A through B: you can access the elements of A through a pointer of type B * (or a link of type B &). But you cannot access the same members via an A * pointer (or A & link).

Just as if you had a member function like this:

 void badfunc() { B b; } 

You are trying to use the Base constructor directly, not through Derived . When you initialize the base in the constructor initialization list, this is a special context that allows you to call the constructor because you are executing it as part of the Derived initialization.

+2
source

These are indeed several questions in one.

About this ...

In my opinion, using this should be completely redundant, but compiler support for C ++ 11 is not completely universal. This should work in accordance with 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 since it appears in the “unpublished operand”, everything is fine. Starting at n3337 sec 4.1.1:

An identifier that denotes a non-static data element or non-stationary function of a class member can only be used:

...

  • if this id expression denotes a non-static data element and it appears in an unvalued operand.

However, some compilers do not support this. Then you are forced to use std::declval :

 #include <utility> struct Base { void func() noexcept; }; struct Derived() { void func() noexcept(noexcept(std::declval<Base>().func())) {} }; 

About availability ...

I read the relevant parts of the standard about “unvalued operands” and “access control for elements”, and I came to the conclusion that the standard is a bit ambiguous. It mentions that the name protected can only be used by members, friends, and derived classes. The question is whether inexperienced operands use the names of the participants that appear in them. They, of course, do not use odr-use member names and can even use member names if no definition is given, and this ambiguity is precisely why the term "odr-use" exists! For instance,

 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 

Although this is somewhat unclear, it’s hard for me to imagine that a C ++ committee could allow you to use remote member functions on unvalued operands but not inaccessible member functions. However, I am not sure.

Please note that the above code compiles without errors with both GCC and Clang. However, the following code does not contain:

 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 little strange, to say the least. The following code is accepted by the Clan, 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.

conclusions

The conclusion is that there seems to be a lot of inconsistency to get around and a lot of gaps between current compilers and the C ++ 11 specs.

+1
source

Source: https://habr.com/ru/post/1244619/


All Articles