Home > Mobile >  C Why can't I throw an abstract class?
C Why can't I throw an abstract class?

Time:06-21

I have a pure virtual class, BaseClass, with no data members and a protected constructor. It exists only to provide the interface for a subclass (which is templated). I want just about all the functionality implemented in the base class. Functions that operate on these typically would return BaseClass&, to allow convenient chaining of operations.

All well and good until I try to throw an instance, returned by reference from one of the functions. In short, I am throwing a reference to an abstract class, but I'm secure in the knowledge that it's guaranteed to be a fully constructed child class instance with all virtual functions intact. That's why the BaseClass constructor is protected. I'm compiling against a C 14 compiler. It's refusing to allow me to throw an abstract class.

One of the main points of the class is that I can construct a subclass, immediately call functions on it to add information, and then throw it, all in one fell swoop:

throw String<72>().addInt(__LINE__).addText("mom says no"); //does not compile

but addText(), very reasonably, returns BaseClass&, and the compiler refuses.

How do I? Moving all the member functions into the subclass seems obscene. I can hack around it by static casting the whole expression before the throw:

throw static_cast<String<72&>( //works, ugly
   BaseClassString<72>().addText(where).addText(why).addText(resolution));

and even create a macro to hide the ugly mechanics and ensure some safety, but am I missing something? This seems like case of C preventing a perfectly workable technique.

CodePudding user response:

Why can't I throw an abstract class?

Due to [except.throw]

/3 Throwing an exception copy-initializes ([dcl.init], [class.copy.ctor]) a temporary object, called the exception object.

/5 When the thrown object is a class object, the constructor selected for the copy-initialization as well as the constructor selected for a copy-initialization considering the thrown object as an lvalue shall be non-deleted and accessible, even if the copy/move operation is elided ([class.copy.elision]).

CodePudding user response:

You could have the derived class throw itself through a virtual void Throw() const member function. That would eliminate the casting at the callsite. The principle being similar to having a virtual clone function for polymorphic copying.

#include <iostream>
#include <sstream>
#include <string>
#include <variant>
#include <vector>

using std::cout;
using std::get;
using std::holds_alternative;
using std::ostream;
using std::ostringstream;
using std::string;
using std::variant;
using std::vector;

class Abc {
    using info_t = variant<int, string>;
    vector<info_t> v;
    virtual auto subtext() const -> string = 0;
public:
    virtual ~Abc();
    Abc() = default;
    Abc(Abc const&) = default;
    auto addInt(int) -> Abc&;
    auto addText(string) -> Abc&;
    virtual void Throw[[noreturn]]() const = 0;
    virtual void print(ostream&) const;
};

Abc::~Abc() = default;

auto Abc::addInt(int value) -> Abc& {
    v.push_back(value);
    return *this;
}

auto Abc::addText(string value) -> Abc& {
    v.push_back(value);
    return *this;
}

void Abc::print(ostream& out) const {
    if (auto st = subtext(); !st.empty())
        out << st << "\n";

    for (auto&& x : v) {
        if (holds_alternative<int>(x)) {
            out << get<int>(x) << "\n";
        } else if (holds_alternative<string>(x)) {
            out << get<string>(x) << "\n";
        } else {
            out << "(unknown type)\n";
        }
    }
}

template <typename T>
class Concrete : public Abc {
    T data;
    auto subtext() const -> string override {
        ostringstream ss;
        ss << data;
        return ss.str();
    }
public:
    Concrete(T value) : data{value} {}
    void Throw[[noreturn]]() const override { throw *this; }
};
struct allow_ctad_t;
Concrete(allow_ctad_t)->Concrete<void>;

int main() {
    try {
        Concrete(1000).addInt(__LINE__).addText(__FILE__).addText("Just testing!").Throw();
    } catch (Abc const& ex) {
        ex.print(cout);
    }
}
  • Related