Reputation: 45414
If using std::map<std::string,object>
to keep objects ordered by name, the map's value_type
(std::pair<string,object>
) is a convenient class which warrants useful functionality, e.g.
typedef std::map<std::string,object> object_map;
typedef typename object_map::value_type named_object;
void function(named_object const&);
// with the following possible use case
void foo(std::string const&name, object_map const &omap)
{
auto obj=omap.find(name);
if(obj==omap.end()) throw std::runtime_error("name not found");
function(*obj);
}
So far, so good. I later extend my objects
struct extended_object : object { /* more data */ };
and keep the extended objects ordered by name, too, via std::map<std::string,extended>
.
I may define.
typedef std::map<std::string,extended_object> extended_object_map;
typedef typename extended_object_map::value_type named_extended_object;
Unfortunately, I cannot
void foo(std::string const&name, extended_object_map const& emap)
{
auto eobj=emap.find(name);
if(eobj==emap.end()) throw std::runtime_error("name not found");
function(*eobj); // cannot convert *eobj to const named_object&
}
Now, my question is how to solve this problem? I considered using a reinterpret_cast<>
function(reinterpret_cast<const named_object&>(*eobj));
which essentially assumes that the data layouts of named_object
and named_extended_object
are exactly like that of base and derived. Is this safe/recommendable?
Alternatively, I considered to use std::set
(instead of std::map
) with key types
named_object
and re-define
struct named_extended_object : named_object { /* more data */ };
The problem with this approach is that in order to std::set::find()
an object, I must provide not just a name string, but a whole object or even extended object. According to cppreference, std::set::find()
will have a resolution to this problem in C++14 (overloads 3&4), but what shall I do in the mean time?
Upvotes: 0
Views: 125
Reputation: 171263
the map's
value_type
(std::pair<string,object>
) is a convenient class which warrants useful functionality
N.B. that is not the map's value_type
. It's std::pair<const string,object>
The reinterpret_cast
results in undefined behaviour when a named_extended_object
is accessed through the reference, it violates what some compilers call "strict aliasing" rules, and can cause your program to misbehave if the compiler optimises using type-based alias analysis. Do not use reinterpret_cast
like that. In handwavy Liskov Substitution Principle terms, extended_object
IS-A object
, so it's OK to substitute an extended_object
where an object
is expected, but pair<T, extended_object>
IS-NOT-A pair<T, object>
so cannot be used interchangeably.
I like D Drmmr's answer, but if you don't want to do that, another option is a template:
template<typename Obj>
void function(const std::pair<const std::string, Obj>&)
{
// ...
}
This will accept pair<const string, object>
and also pair<const string, extended_object>
arguments. If the body of the template only tries to access the members that are present in object
then it will work perfectly for both argument types.
If you don't want to expose that template to users of the function, then declare two overloads:
void function(const named_object&);
void function(const named_extended_object&);
Then in the implementation file:
namespace {
template<typename Obj>
void function_impl(const std::pair<const std::string, Obj>&)
{
// common implementation ...
}
}
void function(const named_object& no) { function_impl(no); }
void function(const named_extended_object& neo) { function_impl(neo); }
Upvotes: 1
Reputation: 1243
Change your function to
void function(const std::string& name, const object& obj);
Then call it like
auto obj = omap.find(name);
function(obj->first, obj->second);
// or
auto eobj = emap.find(name);
function(eobj->first, eobj->second);
Upvotes: 1