3

Is there an efficient and safe way to cast a std::vector<const Point*>& to std::vector<Point*>&?

Doing reinterpret_cast<std::vector<Point*>&>(constvec) would probably work correctly but is probably undefined behavior.

The only standard option seems to be constructing a new std::vector<Point*> and adding each const_casted element manually, but then the program would needlessly allocate memory for it.

Edit: The program looks like this (simplified):

class A {
private:
    int some_data[10];

public:
    template<typename Callback>
    void algo(Callback callback) const {
          std::vector<const int*> values { &some_data[0], &some_data[5], &some_data[3] };
          // Class is const-correct internally, so only have const access to data here.
          // Making it mutable is not easily possible in the real code,
          // as there are constructs similar to iterator/const_iterator in the class hierarchy.
          callback(values);
    }

    template<typename Callback>
    void algo(Callback callback) {
         // Hack to avoid copying the entire algorithm implementation
         auto cb = [&](const std::vector<const int*>& vec) {
             callback(....); // <--- should be const std::vector<int*>
         }
         static_cast<const A*>(this)->algo(cb);
    }
};

Another option would be to implement the algorithm in the non-const variant, and then const_cast<A&>(*this).algo() for the const variant. But this seems more dangerous since the A object might have been created as const (const A a;) and then it is UB.

tmlen
  • 8,533
  • 5
  • 31
  • 84
  • 3
    I can see why you may want to cast `std::vector` to `std::vector`, but the other way round just looks dodgy. What's the point of const if you are just going to ignore it? – Neil Kirk Mar 18 '15 at 18:10
  • I can imagine that there are legitimate use cases, but in order to suggest a solution I'd have to see what the OP is really trying to do. – Brian Bi Mar 18 '15 at 18:12
  • 3
    @NeilKirk even that way is iffy, as if you add a `const Point*` to the cast vector, you have violated const-correctness as now you have a `Point*` in the original vector. It is the mutable square-is-not-a-rectangle problem. If the OP has it backwards, a const-correct *view* of the contents of a vector can be created. – Yakk - Adam Nevraumont Mar 18 '15 at 18:13
  • @Neil Kirk: It is an argument of a callback function. The class is itself const-correct, so the const member function (that calls the callback) can only have const access to the object's data. But instead of copying the code to make a non-const member function with the same algorithm, I tried to make the a-const version which invokes the const one, and passed the callbacks through an intermediary const-cast. – tmlen Mar 18 '15 at 18:15
  • @Yakk What's wrong with a `point *` in a `const point *` data structure? – Neil Kirk Mar 18 '15 at 18:16
  • 4
    @tmlen So your real question is how to avoid duplicating code when creating a const and non-const version of the same function? – Neil Kirk Mar 18 '15 at 18:16
  • 1
    @NeilKirk it is a `const point*` in the `point *` data structure. You add a `const point*` to the `std::vector< const point *>&` cast of the `std::vector< point * >&`, and it arrives in the `std::vector< point* >` illegally. – Yakk - Adam Nevraumont Mar 18 '15 at 18:17
  • ... why are you passing a `std::vector` at all? Is the callback intended to change the size of the `std::vector`, or do they just want the list of elements? – Yakk - Adam Nevraumont Mar 18 '15 at 18:57
  • I've been changing it to invoke the callback for each element of the vector separately, this is also more efficient in this case (no memory allocation, parallelizable). But that still needs a `const_cast` in the non-const function. – tmlen Mar 18 '15 at 19:11

2 Answers2

2
template<class A, class B>
struct as_const_as { using type=B; }
template<class A, class B>
struct as_const_as<const A, B> { using type=const B; }
template<class A, class B>
using as_const_as_t=typename as_const_as<A,B>::type;

private:
  template<class Self, class Callback>
  static void algo(Self* self, Callback callback) {
    // code using self instead of this
    using int_t = as_const_as_t<Self, int>; // const or not depending on self
  }
public:
  template<class Callback>
  void algo(Callback callback) {
    algo( this, callback );
  }
  template<class Callback>
  void algo(Callback callback) const {
    algo( this, callback );
  }

now we have two external methods algo, and one static method that takes its own type as a template type that can be const or not.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • We really need a better way to templatize member functions on the type/cv-qualifiers/value category of `*this` than by forwarding to a static member template. – Casey Mar 18 '15 at 19:48
  • @Casey What, 3*2*2 is too many manual overloads for ya? I really should take `Self` by forwarding `&&` reference, to allow `const&` to be distinguished from `const&&` as you have mentioned. It makes `as_const_as_t` slightly more annoying. – Yakk - Adam Nevraumont Mar 18 '15 at 20:02
  • I used to point out this possibility, with a constness transfer. But I think now it's ungood to have the `Self` type as a template parameter, and that it's generally better to make things explicit. Hence the more advanced :) (in the sense of later version, evolved) shown in my answer posted before this. – Cheers and hth. - Alf Mar 18 '15 at 21:24
1

Except that std::vector is a standard library class, the types vector<Foo*> and vector<Foo const*> might be specialized differently and have different sizes. Thus while a reinterpret_cast will usually work, it's formally Undefined Behavior. And anyway then need for reinterpret_cast (except in some C style contexts such as in Windows API level programming) is generally a strong signal that one is heading up the wrong alley.

In the case where one has non-trivial code of a method that one wants in both const and non-const versions, one solution is to defer to an ordinary function, e.g. a static member function.

To do that it's convenient to have some support machinery:

struct Mutable{};
struct Const{};

template< class Constness, class Type >
struct With_t;

template< class Type > struct With_t<Mutable, Type>{ using T = Type; };
template< class Type > struct With_t<Const, Type>{ using T = Type const; };

template< class Constness, class Type >
using With = typename With_t<Constness, Type>::T;

Then you can write classes such as this:

class Foo
{
private:
    int     something_;

    template< class Constness >
    static auto p( With<Constness, Foo>* that )
        -> With<Constness, int>*
    { return &that->something_; }   // In the real world some non-trivial code here.

public:
    auto p() -> int* { return p<Mutable>( this ); }
    auto p() const -> int const* { return p<Const>( this ); }

    Foo( int const value ): something_( value ) {}
};
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • To enforce mutability (or else compilation error) one might use `std::remove_const`, I think it was called. But I leave that to the reader. Whether to do it is an engineering gut-feeling decision. – Cheers and hth. - Alf Mar 18 '15 at 18:57