6

A class contains a std::vector<int*>. External code needs read-only access to this vector, should not be able to modify the contents (neither the pointers or their contents). Inside the class, the values may change (e.g. double_values(), and so storing them as a std::vector<const int*> is not possible.

Is there a way to return the std::vector<int*> as a std::vector<const int*> without making a copy? It feels like there should be, because const is simply operating at compile time to say what can and cannot be modified.

Code: (compile with g++ -std=c++0x)

class ReadOnlyAccess
{
public:
  ReadOnlyAccess(const std::vector<int*> & int_ptrs_param):
    int_ptrs(int_ptrs_param)
  {
  }
  const std::vector<int*> & get_int_ptrs() const
  {
    return int_ptrs;
  }
  std::vector<const int*> safely_get_int_ptrs() const
  {
    // will not compile (too bad):
    //    return int_ptrs;

    // need to copy entire vector
    std::vector<const int*> result(int_ptrs.size());
    for (int k=0; k<int_ptrs.size(); k++)
      result[k] = int_ptrs[k];
    return result;
  }
  void double_values()
  {
    for (int*p : int_ptrs)
      *p *= 2;
  }
  void print() const
  {
    for (const int * p : int_ptrs)
      std::cout << *p << " ";
    std::cout << std::endl;
  }
private:
  std::vector<int*> int_ptrs;
};

int main() {
  ReadOnlyAccess roa(std::vector<int*>{new int(10), new int(20), new int(100)});
  std::vector<const int*> safe_int_ptrs = roa.safely_get_int_ptrs();
  // does not compile (good)
  // *safe_int_ptrs[0] = -100000;
  roa.print();

  const std::vector<int*> & int_ptrs = roa.get_int_ptrs();
  // changes are made to the internal class values via the accessor! nooooo!
  *int_ptrs[0] = -100000;
  roa.print();

  return 0;
}
user
  • 7,123
  • 7
  • 48
  • 90
  • possible duplicate of [vector and const](http://stackoverflow.com/questions/2102244/vector-and-const) – Bo Persson Apr 22 '12 at 06:25
  • see http://stackoverflow.com/questions/2868485/cast-vectort-to-vectorconst-t – WeaselFox Apr 22 '12 at 06:27
  • 2
    @Bo: That question doesn't answer this one, really. And WeaselFox' is irrelevant too because of the pointer indirection. – Xeo Apr 22 '12 at 06:29
  • @Xeo - The answers to the other question tell us why it doesn't work. What other kind of answer is there? – Bo Persson Apr 22 '12 at 06:32
  • A high-level conceptual workaround would be acceptable. How would you allow access without changing the contents in your code? Is there any way around making the copy (it feels unnecessary, and it may be the factor limiting performance). I could simply do a `void*` cast, but I worry it's bad design. – user Apr 22 '12 at 06:40
  • It seems the only difference between http://stackoverflow.com/questions/2102244/vector-and-const (as @BoPersson linked) and the current question is that, presumably, the current questioner would be satisfied in defining the parameter to `ReadOnlyAccess(...)` as a `const` vector (which it's not in the linked question). Considering the answer to that question, is it true, Bo, that the compiler can't be expected to recognize that it is a 'const' vector and therefore allow the conversion to `vector` in *this* case? – Dan Nissenbaum Apr 22 '12 at 06:44
  • Returning a const vector still allows you to change the values of what the pointers are pointing to. This little program illustrates that. #include #include int main () { std::vector vec; vec.push_back(new int(5)); const std::vector& vec2(vec); *(vec2[0]) = 3; std::cout << *(vec2[0]) << std::endl; } So the OP's question is valid and is not solved by returning a const vector. – BertR Apr 22 '12 at 06:56
  • @Dan - No, that is just not part of the language. A `vector` is not convertible to `vector` even if `T` is convertible to `U`. And not even if the difference between `T` and `U` is very small. – Bo Persson Apr 22 '12 at 07:00
  • @BertR - however, if the template parameter to the argument of the function `ReadOnlyAccess(const std::vector&)` is explicitly defined as `const int*`, as well as the function argument also being a `const vector`, the value being pointed to can't change. Presumably, Bo Persson is correct, and the language does not allow it. However, can you think of another example that would violate consistency, even with the signature `ReadOnlyAccess(const std::vector &)`? – Dan Nissenbaum Apr 22 '12 at 07:17

2 Answers2

4

Returning the vector will imply a copy if you want to keep the const pointers anyway.

However, if your goal is to provide a way to use the values without modifying them, or modifying it's container, then a visitor pattern based algorithm might be a very good solution, in particular now that we can use lambda expressions:

#include <vector>
#include <iostream>

class Data
{
public:

    //...whatever needed to fill the values

    // here we assume that Func is equivalent to std::function< void ( int )> or std::function< void (const int& ) > and can return anything that will be ignored here.
    template< class Func > 
    void for_each_value( Func func ) const // read-only
    {
        for( const int* value : m_values ) // implicit conversion
        {
             func( *value ); // read-only reference (const &), or copy
             // if func needs to work with the adress of the object, it still can by getting a reference to it and using & to get it's adress
        }
    }


    void print() const
    {
        std::cout << "\nData values: \n";
        for_each_value( []( const int value ) { std::cout << "    "<< value << '\n'; } );
    }

    void count_values() const { return m_values.size(); }

private:

    std::vector<int*> m_values;

};



int main()
{
    Data data;
    // ... whatever needed to fill the data

    data.print();    

    std::vector<int> modified_values;
    data.for_each_value( [&]( int value ) { modified_values.push_back( value + 42 ); } );

    return 0;
}

If you understand that, and the different ways to use the values can be reduced to a few half-generic algorithms, then it will make your code simpler and allow you to keep data inside your structures instead of exposing it's the guts.

Klaim
  • 67,274
  • 36
  • 133
  • 188
  • Oviously. Why do you ask, exactly? Here std::for_each used outside of the class wouldn't help as it would require to either expose the real container iterator, or make a copy, that is to be avoided by question. I could have only replaced the loop in for_each_value by a std::for_each, with no benefit. The point of my answer is that there is no copy of the container, and whatever the algorithm for traversal, you don't need to expose the content of the class IF you know what kind of traversal are needed by the users of the class. Obviously, if you don't know, embrace YAGNI. – Klaim Apr 22 '12 at 11:45
  • That being said, some cases require that the class expose the content by iterators. But I don't think that's necessary for the specific question. – Klaim Apr 22 '12 at 11:47
  • So how is your for-range loop expected to work if iterators aren't exposed? And if they are, how is your `for_each_value` different from `std::for_each`? YAGNI is all very well, but to me, you're applying it upside down. Why write your own `for_each` function when you don't need it because the standard library already does it for you? – jalf Apr 22 '12 at 12:28
  • 1
    It looks like I don't understand your question as I think I have answered above. Or maybe we are talking about the same loop? I'm saying exposing iterators is good if it's the purpose of the type, and I add that I don't think it's appropriate here. The type isn't a container, it hold one. It shouldn't expose it's data without control. – Klaim Apr 22 '12 at 13:00
1

You can provide a view to const values via custom iterators. An easy way would be to use boost::iterator:

#include <boost/iterator/indirect_iterator.hpp>

class ReadOnlyAccess
{
// ...
    typedef boost::indirect_iterator<const int* const*, const int> const_val_iter_type;
    const_val_iter_type cval_begin() {
        return it_t{const_cast<const int* const*>(&int_ptrs[0])};
    }
}

int main() {
    // ...
    auto x = roa.cval_begin();
    std::cout << x[0] <<' ' << x[1] << x[2] <<'\n';
    // we can still access the pointers themselves via .base() member function:
    for (int i=0; i<3; ++i)
        assert(x.base()[i] == safe_int_ptrs[i]);
    // the values are read-only, the following does not compile:
    // x[0] = -1;
    // **x.base() = -1;
    // *x.base() = nullptr;
}

If we used boost::indirect_iterator<typename std::vector<int*>::const_iterator, const int> for const_val_iter_type, we could modify the pointed values via .base() (but not directly like in e.g. x[0] = -1), so this solution is not general.

rafak
  • 5,501
  • 2
  • 19
  • 30