6

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?

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 2
    I've tried it in VS 2017 and my result are different (and expected): 1. direct result: thrown 2. sliced result: user_error 3. ranged result: user_error Which compiler do you use? – R2RT Aug 10 '17 at 10:48
  • Which of 2 or 3 was what you expected to be the output of both? – eerorika Aug 10 '17 at 10:52
  • 2
    Add `override` to the override. – molbdnilo Aug 10 '17 at 10:54
  • @R2RT http://ideone.com/QgCPXm – A. Lisowski Aug 10 '17 at 10:58
  • @user2079303 To be honest, i'm not sure. The more I think about it, the more sense my output get. But what standard says? And, as R2RT mentioned, visual produces other output. – A. Lisowski Aug 10 '17 at 11:11
  • 1
    The problem can be simplified to this: http://ideone.com/avu745. `what()` and `user_error::what()` give different results even when called on a `user_error` object. – Anton Aug 10 '17 at 11:54
  • I think case 3 is the error. Case 2 copy constructs a new user_error object (and hence a new std::exception) from the existing one, and calls it's what function -that outputs the argument passed to the original. Case 3 calls the user_error version of what (which is actually the std::exception version), and this *should* output the argument passed to the constructor - but it doesn't seem to. – Martin Bonner supports Monica Aug 10 '17 at 11:57
  • @A.Lisowski Happens on cpp.sh too (once I included ``): http://cpp.sh/2y6dm. Even happened on clang (using with rextester), so I wonder if it's a bug/feature of glibc. – Martin Bonner supports Monica Aug 10 '17 at 12:04
  • Here is another minimal example, that demonstrates the behaviour without getting exceptions involved: http://coliru.stacked-crooked.com/a/5a9d9d259837ef6a – eerorika Aug 10 '17 at 12:06
  • @MartinBonner Not a bug in glibc. More like a lack of feature that VS appears to have. I find VS behaviour quite surprising, but also potentially useful. There is probably some marginal overhead to achieve it, but if `std::exception::what` is a bottle neck, then there is something wrong with the design :) – eerorika Aug 10 '17 at 12:53
  • @user2079303 : It's a bug in Visual Studio - *as you have explained in your answer* (the output is "implementation defined", which means "they have to define it"). – Martin Bonner supports Monica Aug 10 '17 at 13:36
  • @MartinBonner well, if their documentation doesn't define what it is, then it is technically not conformant. Then the "bug" is in the documentation. Their official docs seem to say that the string is left unspecified. I wonder if defining something to be unspecified is sufficient for to satisfy implementation-defined requirement :) Frankly, I don't know if stdlibc++ documentation defines it either. – eerorika Aug 10 '17 at 13:47

1 Answers1

4

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.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • According to above, gcc (2!=3) and visual (2==3) output can be considered legal, is that correct? – A. Lisowski Aug 10 '17 at 12:39
  • Given exception definition as in question, what is the best method to extract message passed to user_error? – A. Lisowski Aug 10 '17 at 12:40
  • @A.Lisowski 1) Correct. It is allowed, and possible with a convoluted implementation of `std::exception` to print the value given to the `str::runtime_error`, but not required. I added my simplified example where the output must differ 2.) `ex.runtime_error::what()`. – eerorika Aug 10 '17 at 12:46
  • (or simply remove the using declaration, which appears to be counter productive). – eerorika Aug 10 '17 at 12:55
  • This simplified example does not show one thing. user_error has other what method (with parameters). In that situation using is necessary. – A. Lisowski Aug 10 '17 at 12:58
  • 1
    @A.Lisowski Then use `using std::runtime_error::what` instead. – eerorika Aug 10 '17 at 13:02
  • Then, do what I suggested before I suggested you modify user_error: `ex.runtime_error::what()` – eerorika Aug 10 '17 at 13:07
  • @user2079303 : I think you are wrong about the visual studio implementation being legal - it is *implementation defined* which means the implementation must define what it is going to be. (And as far as I can see, they don't.) – Martin Bonner supports Monica Aug 10 '17 at 13:43
  • Hmm. The VC devs obviously don't agree with me - it *does* get your minimal example (not involving std::exception) right. They must have decided to push the variable message stuff down into std::exception (and not very convoluted). – Martin Bonner supports Monica Aug 10 '17 at 13:47
  • @MartinBonner Yeah, not convoluted after all. It seems to be pretty trivially enabled by VS extension that adds exception message to the base class. My first guess was some internal virtual dispatch, which is what I would have described convoluted. – eerorika Aug 10 '17 at 13:54
  • @user2079303 I've checked VS implementation and I guess the difference here is made by fact that `std::runtime_error` inherits from `std::exception` but does not override `what`, so `using std::exception::what;` does not skip any function in inheritance chain, thus result is the same function call. – R2RT Aug 10 '17 at 14:15