6

During the member initialisation of a class with multiple members, it seems desirable to be able to catch an exception generated by any specific member initialiser, to wrap in additional context for rethrowing, but the syntax of a function-try-block doesn't appear to accomodate that.

#include <stdexcept>
#include <string>
#include <sstream>

using namespace std::literals::string_literals;

[[noreturn]]
int thrower() { throw std::runtime_error("unconditional throw"s); }
int nonThrower() { return 3; }

class C {
    int x;
    int y;

    public:
    C();
};

class XError : public std::runtime_error {
    public:
    XError(const std::string& what) : std::runtime_error((std::stringstream() << "xerror: "s << what).str()) {};
};

class YError : public std::runtime_error {
    public:
    YError(const std::string& what) : std::runtime_error((std::stringstream() << "yerror: "s << what).str()) {};
};

C::C() try:
    x(nonThrower()),
    y(thrower()) {}
    catch(const std::exception& e) { /* ... */ }

In the above trivial example, on the final line, I would very much like to be able to throw XError() if the exception occurs in initialisation of .x, and YError() if the exception occurs in initialisation of .y.

Ideally I'd like to be able to do something like

// -std=c++3000
C::C():
    try: x(nonThrower()) catch(const std::exception& e) { throw XError(e.what()); },
    try: y(thrower()) catch(const std::exception& e) { throw YError(e.what()); } {}

But it seems my only option is to write separate functions per initialiser member which wrap the exceptions themselves, then use those in the member initialisers. Given how ghastly member initialiser syntax is, this will probably read better, but it's less compact and more non-linear, which is less than ideal. For completeness:

int makeX() {
    try { return nonThrower(); }
    catch(const std::exception& e) { throw XError(e.what()); }
}

int makeY() {
    try { return thrower(); }
    catch(const std::exception& e) { throw YError(e.what()); }
}

C::C():
    x(makeX()),
    y(makeY()) {}

Please excuse any antipatterns or mistakes in my C++. I am both very new to the language and not as smart as you might hope me to be.

Adam Barnes
  • 2,922
  • 21
  • 27
  • 2
    Shouldn't this question have the `c++3000` tag? ;-) – Adrian Mole Jul 17 '22 at 07:35
  • 1
    Still waiting on WG21 to let me add it to Stack Overflow. – Adam Barnes Jul 17 '22 at 07:36
  • Why do you need to trap and wrap exceptions? Why not just deal with whatever exceptions are thrown from the code that is trying to create the object? Are you perhaps micromanaging the exceptions a little? – Galik Jul 17 '22 at 08:06
  • Imagine I'm writing a graphics library and fail to create a window upon which to display the graphics. Perhaps the windowing library I'm using for Windows throws `ThisWentWrongSadFace()`, the one on Linux throws `__e_sig_ill()`, and the one on Mac has me check the return value of `lastCallSucceeded()`. None of that matters to my user, they just wanted to draw a rotating square. My user should be able to read my docs and see that I can and will throw `WindowCreationError` when I fail to create an error, catch that, and go on with their day. – Adam Barnes Jul 17 '22 at 08:09
  • 1
    No, you can't do that. Why not change specification of `nonThrower()` and `thrower()` so they throw `XError` and`YError` respectively? And what's wrong with your option of wrapping each one in a function that swallows the `std::exception` and then throws the desired exception? Either way, you don't need to worry about doing the mapping in the constructor of `C` (or its initialiser list) - which is particularly handy if `C` has multiple constructors (since you then don't have to map the exceptions in every constructor). – Peter Jul 17 '22 at 08:49
  • "Why not change the specification" because I didn't write that code. "What's wrong with your option of wrapping each one in a function" nonlinearity and a want for function-try-blocks to work better as noted in the question. The multiple constructors point is an excellent one which I haven't encountered yet, thank you for noting it. – Adam Barnes Jul 17 '22 at 12:23
  • 1
    If the error is for example `File not found` does it matter if it was when initializing `x` or `y`? Isn't the relevant information the "filename" it tried to open? There are also 2 more ways: 1) don't use an initializer list but the body of the constructor (often bad). 2) `x([]() { try { return nonThrower(); } catch(const std::exception& e) { throw XError(e.what()); }())`. (untested) – Goswin von Brederlow Jul 17 '22 at 15:16
  • It does - imagine if both `x` and `y` can throw the same error for different reasons. Perhaps initialising `x` throws `std::runtime_error()` with a message about failing to create a network socket, and initialising `y` throws the same error with a message about disk space. With a function-try-block I'd have to do string matching on the error (yuck) even though it's much more sensible to simply know from whence it came. – Adam Barnes Jul 20 '22 at 08:12

2 Answers2

8

Is it possible to have a function-try-block per member initialiser?

No, that is not possible.

Sidebar: it seems like you're overusing and/or overthinking exceptions. Most people don't write much exception-handling code, because most programs are fine with just terminating if an exception is thrown, in most places. Of course there are exceptions to this rule, but if you're new to C++ and you're getting hung up on this, you should probably revisit your approach and not make your program so reliant on fine-grained exception handling.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • Perhaps this is best stuited for another question but that (lack of exception-orientedness) seems suboptimal to my beginner eyes. Without exceptions and handling code, callers can end up with objects that failed to initialise and are in a nonsense state, and they won't know unless they _elect_ to check `someLibrary::lastCallWorkedProperly()` or something. Obviously I'm wrong, but why am I wrong? – Adam Barnes Jul 17 '22 at 07:47
  • 2
    I didn't say there should be no exception throwing. I said there should be very little exception catching. If something goes so wrong that your program throws an exception, in most programs you may as well just let it terminate. But again, you're new to C++, focus on other aspects first, this is a rabbit hole that isn't as important as it seems to you now. – John Zwinck Jul 17 '22 at 08:02
  • 1
    I'll file the concern of mine away for now then, but this is all learning for personal reasons - I don't program in C++ for money, so if it's interesting to me then it's important. Thank you John. :) – Adam Barnes Jul 17 '22 at 08:04
7

It's not possible, but if you want to go down the exception route you can write function wrappers:

template<typename Exn, typename T>
T catch_with(T (*fn)()) // or, std::function<T()> fn
{
    try { return fn(); }
    catch(const std::exception& e) { throw Exn(e.what()); }
}


C::C():
    x(catch_with<XError>(nonThrower)),
    y(catch_with<YError>(thrower) {}
molbdnilo
  • 64,751
  • 3
  • 43
  • 82