I have an expr_t
base class, from which ident_t
is derived. I wrote some to_string
overloads to display differently between expr_t
and ident_t
:
#include <iostream>
#include <string>
struct expr_t {
virtual ~expr_t() {}
};
struct ident_t : public expr_t {
std::string name;
ident_t(std::string name) : name(name) {}
};
std::string to_string(expr_t& v) {
if (auto* id = dynamic_cast<ident_t*>(&v)) {
return to_string(*id);
}
return "error";
}
std::string to_string(ident_t& v) {
return v.name;
}
int main() {
expr_t* b = new ident_t("c");
std::cout << to_string(*b) << std::endl; // segfault
delete b;
return 0;
}
However, no output is generated and seg-fault occurs when debugging with GDB.
CodePudding user response:
The problem is that at the point of the call return to_string(*id)
the compiler doesn't have a declaration for the second overload std::string to_string(ident_t& v)
. Thus the same first version will be recursively called eventually resulting in a seg fault.
To solve this you can either move the second overload's definition before the first overload or forward declare the second overload before the definition of the first overload as shown below:
//moved the 2nd overload above first overload
std::string to_string(ident_t& v) {
return v.name;
}
std::string to_string(expr_t& v) {
if (auto* id = dynamic_cast<ident_t*>(&v)) {
return to_string(*id); //now compiler knows about std::string to_string(ident_t& v)
}
return "error";
}
Method 2
//forward declaration for 2nd overload
std::string to_string(ident_t& v);
std::string to_string(expr_t& v) {
if (auto* id = dynamic_cast<ident_t*>(&v)) {
return to_string(*id); //now compiler knows about std::string to_string(ident_t& v)
}
return "error";
}
//definition for 2nd overload
std::string to_string(ident_t& v) {
return v.name;
}
CodePudding user response:
As mentioned in the comments, at the point where you call (or try to call) the version of to_string
that takes an ident_t&
argument, there has been neither definition nor declaration of such an overload. Thus, that call becomes infinitely recursive, causing (eventually) a crash.
To resolve the issue either: (a) move the definition of the ident_t&
-taking overload to before the expr_t&
version; or (b) at least provide a declaration of that overload before the call:
std::string to_string(ident_t& v); // Declaration of the other version ...
std::string to_string(expr_t& v)
{
if (auto* id = dynamic_cast<ident_t*>(&v)) {
return to_string(*id); // ... which can now be used here!
}
return "error";
}
(You could even add that declaration immediately before the call – i.e. inside the if (auto* id ...)
block; however, although such local function declarations are technically legal, many C programmers don't like them.)