Reputation: 63124
I just found something that looks like a quirk to me. Consider :
struct Tile {
Tile(Map &map, int, int)
: map(map) { }
void destroy();
void display() const;
Map ↦
};
This (stripped down) class is an accessor object. It's constructed by the Map
itself as such :
Tile Map::operator ()(int x, int y) {
return Tile(*this, x, y);
}
Tile const Map::operator ()(int x, int y) const {
return Tile(*this, x, y);
}
So a Map
can return a Tile
from which we can call destroy()
(which updates the map), and a Map const
can only return a Tile const
, from which we can only call the non-modifying display()
method.
So everything's good, right ? Well, not quite. Because even though it seemed pretty straightforward at first, I can't figure out how to construct a Tile from a Map const
, because of the Map&
constructor parameter.
I also tried removing Tile
's constructor and aggregate-initializing it, to no avail :
Tile const Map::operator ()(int x, int y) const {
return { *this, x, y };
}
... which looks even stranger to me, since I get an...
error: invalid initialization of reference of type ‘Map&’ from expression of type ‘const Map’
... even though a Tile const
should only contain const
fields, shouldn't it ?
Why does the compiler complain on that last one (the first is quite logical), and can I do something short of rewriting the whole Tile class specifically for const
access ? Could it be one of the mythical places where const_cast
is right ?
Thanks in advance.
Upvotes: 0
Views: 433
Reputation: 303067
Daniel's on the right track - you definitely need a Tile
and ConstTile
class, which can be templated for simplicity, but you need to deal with when you can call destroy()
and how you can construct them. To that end:
template<class MapT>
struct TileT {
TileT(MapT &map, int, int)
: map(map) { }
// we want to be able to construct ConstTile from Tile
template <typename M>
TileT(const TileT<M>& tile)
: map(tile.map) { }
void destroy() {
static_assert(!std::is_const<MapT>::value, "Cannot call destory() from ConstTile");
// rest of implementation
}
void display() const;
MapT ↦
};
using Tile = TileT<Map>;
using ConstTile = TileT<const Map>;
That will give you the desired functionality and will work in a similar fashion to how iterator
/const_iterator
work. So you can do stuff like:
Map map;
...
ConstTile tile = map(4,3); // non-const map, ConstTile is ok
Upvotes: 3
Reputation: 1053
I see no way around creating two versions of the Tile class: One for const access and one for mutable access. Consider the following: What should the destroy function do, if the Map reference is const?
If you want to get around making a Tile and a ConstTile version of the class, you can use templates to achieve the same effect and still avoid code duplication.
template<class MapT>
struct Tile {
Tile(MapT &map, int, int)
: map(map) { }
template<typename U = MapT>
std::enable_if<std::is_const<U>::value> destroy();
void display() const;
MapT ↦
};
MapT can now be Map or const Map depending on the instantiation.
Upvotes: 2