Consider a hierarchy
base_dir -> base_dir_ext -> server
\--> location
with the following reduced code containing a method that, through inheritance, sometimes accepts a server, sometimes a location, and needs to instantiate a location from it. Using the following main(), everything works fine and the location copy-constructor is entered.
#include <iostream>
class base_dir
{
// shared inherited member
public:
base_dir() { };
virtual ~base_dir() { };
};
class base_dir_ext : public base_dir
{
// even more shared inherited members
public:
base_dir_ext() { };
virtual ~base_dir_ext() { };
base_dir_ext(const base_dir_ext &other);
base_dir_ext(const base_dir &other);
};
class server : public base_dir_ext
{
public:
server() { };
~server() { };
server(const server &other);
// server(const base_dir &other);
server &operator=(const server &other);
};
class location : public base_dir_ext
{
public:
location() { };
~location() { };
location(const location&) : base_dir_ext()
{
std::cout << "COPY-CTOR FOR LOCATION ENTERED" << std::endl;
}
location(const base_dir&)
{
std::cout << "COPY-CTOR FOR BASE_DIR ENTERED" << std::endl;
}
location(const server&) : base_dir_ext()
{
std::cout << "COPY-CTOR FOR SERVER ENTERED" << std::endl;
}
};
base_dir *process_location(base_dir *parent)
{
std::cout << "typeid: " << typeid(*(dynamic_cast<location*>(parent) ? dynamic_cast<location*>(parent) : parent)).name() << std::endl;
location loc((dynamic_cast<location*>(parent) ? dynamic_cast<location&>(*parent) : dynamic_cast<server&>(*parent)));
// process location and add it to `parent`
return (parent);
}
int main()
{
location loc;
base_dir *parent = &loc;
process_location(parent);
}
Output:
typeid: 8location
COPY-CTOR FOR LOCATION ENTERED
However, as you may have noticed, I had to comment out server::server(const base_dir &other), i.e. server's constructor that takes base_dir. When I uncomment it,
#include <iostream>
class base_dir
{
// shared inherited member
public:
base_dir() { };
virtual ~base_dir() { };
};
class base_dir_ext : public base_dir
{
// even more shared inherited members
public:
base_dir_ext() { };
virtual ~base_dir_ext() { };
base_dir_ext(const base_dir_ext &other);
base_dir_ext(const base_dir &other);
};
class server : public base_dir_ext
{
public:
server() { };
~server() { };
server(const server &other);
server(const base_dir &other);
server &operator=(const server &other);
};
class location : public base_dir_ext
{
public:
location() { };
~location() { };
location(const location&) : base_dir_ext()
{
std::cout << "COPY-CTOR FOR LOCATION ENTERED" << std::endl;
}
location(const base_dir&)
{
std::cout << "COPY-CTOR FOR BASE_DIR ENTERED" << std::endl;
}
location(const server&) : base_dir_ext()
{
std::cout << "COPY-CTOR FOR SERVER ENTERED" << std::endl;
}
};
base_dir *process_location(base_dir *parent)
{
std::cout << "typeid: " << typeid(*(dynamic_cast<location*>(parent) ? dynamic_cast<location*>(parent) : parent)).name() << std::endl;
location loc((dynamic_cast<location*>(parent) ? dynamic_cast<location&>(*parent) : dynamic_cast<server&>(*parent)));
// process location and add it to `parent`
return (parent);
}
int main()
{
location loc;
base_dir *parent = &loc;
process_location(parent);
}
a compiler error is generated:
$ c++ -Wall -Wextra -Werror main.cpp
main.cpp: In function 'base_dir* process_location(base_dir*)':
main.cpp:54:48: error: operands to ?: have different types 'location' and 'server' location loc((dynamic_cast<location*>(parent) ? dynamic_cast<location&>(*parent) : dynamic_cast<server&>(*parent)));
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:54:48: note: and each type can be converted to the other
My question is: why does this happen? Why all of a sudden server and location appear to be different classes? Don't they count as equivalent as they have the same parent and grandparent?
To get rid of this error, I tried removing dynamic cast of parent to server& type in process_location():
#include <iostream>
class base_dir
{
// shared inherited member
public:
base_dir() { };
virtual ~base_dir() { };
};
class base_dir_ext : public base_dir
{
// even more shared inherited members
public:
base_dir_ext() { };
virtual ~base_dir_ext() { };
base_dir_ext(const base_dir_ext &other);
base_dir_ext(const base_dir &other);
};
class server : public base_dir_ext
{
public:
server() { };
~server() { };
server(const server &other);
server(const base_dir &other);
server &operator=(const server &other);
};
class location : public base_dir_ext
{
public:
location() { };
~location() { };
location(const location&) : base_dir_ext()
{
std::cout << "COPY-CTOR FOR LOCATION ENTERED" << std::endl;
}
location(const base_dir&)
{
std::cout << "COPY-CTOR FOR BASE_DIR ENTERED" << std::endl;
}
location(const server&) : base_dir_ext()
{
std::cout << "COPY-CTOR FOR SERVER ENTERED" << std::endl;
}
};
base_dir *process_location(base_dir *parent)
{
std::cout << "typeid: " << typeid(*(dynamic_cast<location*>(parent) ? dynamic_cast<location*>(parent) : parent)).name() << std::endl;
location loc((dynamic_cast<location*>(parent) ? dynamic_cast<location&>(*parent) : *parent));
// process location and add it to `parent`
return (parent);
}
int main()
{
location loc;
base_dir *parent = &loc;
process_location(parent);
}
But then, while typeid() clearly outputs location type, location's const base_dir& constructor is invoked instead of its own copy-constructor. Why?
The output:
typeid: 8location
COPY-CTOR FOR BASE_DIR ENTERED
Why does the only presence of server(const base_dir &other); ruin the ternary? I do not wish to remove this method since I want my classes to be versatile. Is there anything I miss about inheritance that doesn't let my code add up? I'd prefer to preserve the first version of code with the server constructor method uncommented: is that possible?
The important part of the error message is the last sentence:
I seems like you are not aware of what the conditional operator actually does. It is not a drop in replacment for an
if-else. This will compile without issues (after addingserver(const base_dir &other)back in):Live Demo
However, this is still too complicated. Rather than doing such casts and then conditionally calling one or the other constructor. The constructor could take a reference to base and do the checks internally.
The issue with the conditional operator is that the type of the conditional operator is the common type of the last two operands. So lets look closely:
Its
location&andserver&. Aservercan be converted to alocationvialocation::location(const server&)and alocationcan be converted to aserverviaserver::server(const base_dir &other). Hence the resulting error message. Without theserver::server(const base_dir&)converting cosntructor the resulting type of the conditional operator islocation&, which actually makes the use of it here pointless. You cannot use the conditional operator for dynamic casts like this.For more details I refer you to https://en.cppreference.com/w/cpp/language/operator_other and https://en.cppreference.com/w/cpp/types/common_type.
Here is a simpler example to demonstrate how the conditional operator messes up your casting:
Output:
Because for
xthe cast fails while forythe cast succeeds. Though in either case the conditional operator makes the result abar&irrespective of the different type it is cast to before. The common type ofbar&andfoo&isbar&, because afoo&can be converted to abar&(but not vice versa).