views:

83

answers:

4

Is there a way to make a non-resizeable vector/array of non-reassignable but mutable members? The closest thing I can imagine is using a vector<T *> const copy constructed from a temporary, but since I know at initialization how many of and exactly what I want, I'd much rather have a block of objects than pointers. Is anything like what is shown below possible with std::vector or some more obscure boost, etc., template?

// Struct making vec<A> that cannot be resized or have contents reassigned.
struct B {
  vector<A> va_; // <-- unknown modifiers or different template needed here
  vector<A> va2_;

  // All vector contents initialized on construction.
  Foo(size_t n_foo) : va_(n_foo), va2_(5) { }

  // Things I'd like allowed: altering contents, const_iterator and read access.
  good_actions(size_t idx, int val) {
    va_[idx].set(val);

    cout << "vector<A> info - " <<  " size: " << va_.size() << ", max: "
      << va_.max_size() << ", capacity: " << va_.capacity() << ", empty?: "
      << va_.empty() << endl;

    if (!va_.empty()) {
      cout << "First (old): " << va_[0].get() << ", resetting ..." << endl;
      va_[0].set(0);
    }

    int max = 0;
    for (vector<A>::const_iterator i = va_.begin(); i != va_.end(); ++i) {
      int n = i->get();
      if (n > max) { max = n; }
      if (n < 0)   { i->set(0); }
    }
    cout << "Max : " << max << "." << endl;
  }

  // Everything here should fail at compile.
  bad_actions(size_t idx, int val) {
    va_[0]    = va2_[0];
    va_.at(1) = va2_.at(3);

    va_.swap(va2_);
    va_.erase(va_.begin());
    va_.insert(va_.end(), va2_[0]);

    va_.resize(1);
    va_.clear();
    // also: assign, reserve, push, pop, .. 
  }
};
+1  A: 

Could you create a class which holds a reference to your object, but its constructors are only accessible to its std::vector's friend?

e.g.:

template<typename T>
class MyRef {
   firend class std::vector< MyRef<T> >
public:
   T& operator->();
[...etc...]
Gianni
Maybe create a wrapper for vector that uses this behind the scenes for added encapsulation.
Cogwheel - Matthew Orlando
+2  A: 

There is an issue with your requirements. But first let's tackle the fixed size issue, it's called std::tr1::array<class T, size_t N> (if you know the size at compile time).

If you don't know it at compile time, you can still use some proxy class over a vector.

template <class T>
class MyVector
{
public:
  explicit MyVector(size_t const n, T const& t = T()): mVector(n,t) {}

  // Declare the methods you want here
  // and just forward to mVector most of the time ;)

private:
  std::vector<T> mVector;
};

However, what is the point of not being assignable if you are mutable ? There is nothing preventing the user to do the heavy work:

class Type
{
public:
  int a() const { return a; }
  void a(int i) { a = i; }

  int b() const { return b; }
  void b(int i) { b = i; }
private:
  Type& operator=(Type const&);

  int a, b;
};

Nothing prevents me from doing:

void assign(Type& lhs, Type const& rhs)
{
  lhs.a(rhs.a());
  lhs.b(rhs.b());
}

I just want to hit you on the head for complicating my life...

Perhaps could you describe more precisely what you want to do, do you wish to restrict the subset of possible operations on your class (some variables should not be possible to modify, but other could) ?

In this case, you could once again use a Proxy class

class Proxy
{
public:
  // WARN: syntax is screwed, but `vector` requires a model
  // of the Assignable concept so this operation NEED be defined...
  Proxy& operator=(Proxy const& rhs)
  {
    mType.a = rhs.mType.a;
    // mType.b is unchanged
    return *this;
  }

  int a() const { return mType.a(); }
  void a(int i) { mType.a(i); }      

  int b() const { return mType.b(); }

private:
  Type mType;
};

There is not much you cannot do with suitable proxies. That's perhaps the most useful pattern I have ever seen.

Matthieu M.
I agree, there is no point in allowing mutation but disallowing assignment.
FredOverflow
+1  A: 

What you're asking is not really possible.

The only way to prevent something from being assigned is to define the operator = for that type as private. (As an extension of this, since const operator = methods don't make much sense (and are thus uncommon) you can come close to this by only allowing access to const references from your container. But the user can still define a const operator =, and you want mutable objects anyways.)

If you think about it, std::vector::operator [] returns a reference to the value it contains. Using the assignment operator will call operator = for the value. std::vector is completely bypassed here (except for the operator[] call used to get the reference in the first place) so there is no possibility for it (std::vector) to in any way to override the call to the operator = function.

Anything you do to directly access the members of an object in the container is going to have to return a reference to the object, which can then be used to call the object's operator =. So, there is no way a container can prevent objects inside of it from being assigned unless the container implements a proxy for the objects it contains which has a private assignment operator that does nothing and forwards other calls to the "real" object, but does not allow direct access to the real object (though if it made sense to do so, you could return copies of the real object).

SoapBox
That's not entirely correct. The `std::vector` itself may be declared `const`. Once that is done, the `const` form of `std::vector::operator[]` is called, which returns a `const` reference to the object. Once that is done, only `const` methods in the object may be called through that `const` reference. Those `const` methods can only modify data members that are declared `mutable`. AFAICT, this behavior is what @Jeff is looking for. See my answer for an example.
Void
I believe what he wants is a way to return non-const objects from the const vector (so that he doesn't have to have const members and mutable data in order to work with them).Also, as I mentioned, you can make a const operator = which will assign the mutable data members, so you can still assign to a const object in that case.
SoapBox
@SoapBox: What's wrong with having `const` methods and `mutable` data? The question focuses on how to achieve the desired behavior. `const` methods and `mutable` data is certainly one way to do so without resorting to proxies, `friend` s, etc.
Void
@SoapBox: Regarding making a `const` `operator=`, why would one do that? The expected semantics of `operator=` is assignment, which requires modification of the object. Making that operator `const` implies that no assignment will occur. It certainly doesn't make sense to do so in this particular case.
Void
Well if you are talking about having all of this mutable data and const members to modify it, then you can also have a const operator = to assign to it. Thus defeating the purpose of const completely, which seems to be what you're advocating.
SoapBox
@SoapBox: Of course I'm not advocating defeating `const` completely. Jeff's question was about whether it was possible to enforce specific compile- and run-time behaviors with standard C++ and the STL. I claim that it is possible to do so using the approach I described. I never it said was pretty or the ideal solution. It is just one potental solution. Furthermore, just because it is possible to have a `const` `operator=` doesn't mean it should be made `const`. There's no point in doing that in this case.
Void
A: 

You can achieve what you want by making the std::vector const, and the vector's struct or class data mutable. Your set method would have to be const. Here's an example that works as expected with g++:

#include <vector>

class foo
{ 
public:
  foo () : n_ () {}
  void set(int n) const { n_ = n; }

private:

  mutable int n_;
};

int main()
{
  std::vector<foo> const a(3);  // Notice the "const".
  std::vector<foo> b(1);

  // Executes!
  a[0].set(1);

  // Failes to compile!
  a.swap(b);
}

That way you can't alter the vector in any way but you can modify the mutable data members of the objects held by the vector. Here's how this example compiles:

g++ foo.cpp 
foo.cpp: In function 'int main()':
foo.cpp:24: error: passing 'const std::vector<foo, std::allocator<foo> >' as 'this' argument of 'void std::vector<_Tp, _Alloc>::swap(std::vector<_Tp, _Alloc>&) [with _Tp = foo, _Alloc = std::allocator<foo>]' discards qualifiers

The one disadvantage I can think of is that you'll have to be more aware of the const-correctness of your code, but that's not necessarily a disadvantage either.

HTH!

EDIT / Clarification: The goal of this approach is not defeat const completely. Rather, the goal is to demonstrate a means of achieving the requirements set forth in the OP's question using standard C++ and the STL. It is not the ideal solution since it exposes a const method that allows alteration of the internal state visible to the user. Certainly that is a problem with this approach.

Void