2

This question cites the C++ standard to demonstrate that the alignment and size of CV qualified types must be the same as the non-CV qualified equivalent type. This seems obvious, because we can implicitly cast an object of type T to a const T& using static_cast or reinterpret_cast.

However, suppose we have two types which both have the same member variable types, except one has all const member variables and the other does not. Such as:

typedef std::pair<T, T> mutable_pair;
typedef std::pair<const T, const T> const_pair;

Here, the standard does not allow us to produce a const_pair& from an instance of mutable_pair. That is, we cannot say:

mutable_pair p;
const_pair& cp = reinterpret_cast<const_pair&>(p);

This would yield undefined behavior, as it is not listed as a valid use of reinterpret_cast in the standard. Yet, there seems to be no reason, conceptually, why this shouldn't be allowed.

So... why should anyone care? You can always just say:

const mutable_pair& cp = p;


Well, you might care in the event you only want ONE member to be const qualified. Such as:

typedef std::pair<T, U> pair;
typedef std::pair<const T, U> const_first_pair;

pair p;
const_first_pair& cp = reinterpret_cast<const_first_pair&>(p);

Obviously that is still undefined behavior. Yet, since CV qualified types must have the same size and alignment, there's no conceptual reason this should be undefined.

So, is there some reason the standard doesn't allow it? Or is it simply a matter that the standard committee didn't think of this use case?


For anyone wondering what sort of use this could have: in my particular case, I ran into a use case where it would have been very useful to be able to cast a std::pair<T, U> to a std::pair<const T, U>&. I was implementing a specialized balanced tree data structure that provides log(N) lookup by key, but internally stores multiple elements per node. The find/insert/rebalance routines requires internal shuffling of data elements. (The data structure is known as a T-tree.) Since internal shuffling of data elements adversely affects performance by triggering countless copy constructors, it is beneficial to implement the internal data shuffling to take advantage of move constructors if possible.

Unfortunately... I also would have liked to be able to provide an interface which meets the C++ standard requirements for AssociativeContainer, which requires a value_type of std::pair<const Key, Data>. Note the const. This means individual pair objects cannot be moved (or at least the keys can't). They have to be copied, because the key is stored as a const object.

To get around this, I would have liked to be able to store elements internally as mutable objects, but simply cast the key to a const reference when the user access them via an iterator. Unfortunately, I can't cast a std::pair<Key, Data> to a std::pair<const Key, Data>&. And I can't provide some kind of workaround that returns a wrapper class or something, because that wouldn't meet the requirements for AssociativeContainer.

Hence this question.

So again, given that the size and alignment requirements of a CV qualified type must be the same as the non-CV qualified equivalent type, is there any conceptual reason why such a cast shouldn't be allowed? Or is it simply something the standard writers didn't really think about?

Community
  • 1
  • 1
Siler
  • 8,976
  • 11
  • 64
  • 124
  • 1
    "we can implicitly cast an object of type `T` to a `const T&` using `static_cast` or `reinterpret_cast`." ...whut? If you have to type a cast operation, then it is, by definition, not an implicit cast. Anyway, this is exactly what `const_cast` is for, not either of the other ones; sure, they'll work, but why not use the semantically most relevant keyword? – underscore_d Aug 11 '16 at 21:18

1 Answers1

4

Having a type as a template parameter does not mean that you won't have different alignments, the class contents could be changed, e.g., via specialization or template metaprogramming. Consider:

template<typename T> struct X { int i; };
template<typename T> struct X<const T> { double i; };

template<typename T> struct Y {
    typename std::conditional<std::is_const<T>::value, int, double>::type x;
};
goldwin-es
  • 956
  • 6
  • 11
  • The simple answer is that different template arguments create different and incompatible template types, and `T` and a `T const` are different template arguments... but that can seem a bit arbitrary or circular, hence the OP wondering why e.g. the two aren't special-cased to produce different-but-compatible (castable) template types. This is a good example of just one of the reasons - which, when one starts to ponder, might quickly multiply! - that cv-qualification is considered as a type discriminator here. – underscore_d Aug 11 '16 at 21:22