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);
}
}