Consider following code:
class user_error : public std::runtime_error
{
public:
using std::exception::what;
explicit user_error(const std::string& what_arg):std::runtime_error(what_arg){}
};
class with_overriden_what : public user_error {
public:
with_overriden_what(const std::string& val) : user_error("user_error"), message(val) { }
std::string message;
virtual const char* what() const noexcept {
return message.c_str();
}
};
with this calls:
with_overriden_what ex("thrown");
std::cout << "1. direct result: " << ex.what() << "\n";
std::cout << "2. sliced result: " << static_cast<user_error>(ex).what() << "\n";
std::cout << "3. ranged result: " << ex.user_error::what() << "\n";
It is surprise for me that result of 2 and 3 is different:
1. direct result: thrown
2. sliced result: user_error
3. ranged result: std::exception
Q: Is there a paragraph in standard that address this behaviour?
The only unusual aspect is that, within child class method definitions, you can't directly access parent class instance variables. For example, if the parent had a height instance variable, child class method definitions wouldn't be able to access this directly.
The difference between 2. and 3. is that 2. uses dynamic (== virtual) dispatch (== call). Dynamic dispatch is implicitly used, when a virtual function is called (see later paragraph for exception). Therefore 2. calls the most derived override, which is std::runtime_error::what
which prints the message "user_error"
that was given to the constructor, as required by the post condition of the constructor:
[runtime.error]
runtime_error(const char* what_arg);
4 Effects: Constructs an object of class runtime_error.
5 Postcondition:
strcmp(what(), what_arg) == 0
.
Function call using a scope resolution operator does static dispatch even if the function is virtual.
[class.virtual]
15 Explicit qualification with the scope operator (5.1) suppresses the virtual call mechanism.
Therefore overrides do not matter for 3. What matters is name resolution. The using declaration is like any other member declaration in that it hides the same name that would have otherwise been resolved from a parent.
So, user_error::what
hides std::runtime_error::what
. And, user_error::what
is defined by std::exception::what
.
Now, what should this non virtually called std::exception::what
return according to the standard? (annotated by me):
[exception]
7 Returns: An implementation-defined NTBS. (null terminated string)
Clearly, there is no requirement to print anything in particular, such as printing a string that was passed to a constructor of a derived class that contains this as sub object. Any string is standard compliant.
A minimal example for the behaviour, that does not involve exceptions:
#include <iostream>
struct A {
virtual void x() {
std::cout << "A\n";
}
};
struct B : A {
void x() {
std::cout << "B\n";
}
};
struct C : B {
using A::x;
};
int main() {
C c;
c.x();
c.C::x();
return 0;
}
The output of the two lines must be different.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With