Michael Naifield Michael Naifield - 3 months ago 14
C++ Question

Const lock wrapper for STL containers

I'm trying to create a wrapper for an

std::vector
(or any other container from STL, if possible) that can "lock" and "unlock" the const state of a vector that it's holding.

For example, if I create an object of that wrapper, I want to be able to do something like this:

int main()
{
ConstLockVectorWrapper<int> myWrapper(std::vector<int>{}); // Here I pass an empty vector in the constructor parameters,
// which means that my wrapper will be holding an empty vector

// By default the vector inside my wrapper is not locked,
// I can change its size and the values that it holds

myWrapper.get().push_back(10); // ok
myWrapper.get().push_back(20); // ok
myWrapper.get().at(0) = 5; // ok

print(myWrapper.get()); // Prints 5 20



myWrapper.lock(); // Now I made the vector inside my wrapper unchangable



myWrapper.get().push_back(30); // error, the vector is locked
myWrapper.get().at(0) = 55; // error

print(myWrapper.get()); // ok



myWrapper.unlock(); // Now I can change my vector's size and its values again


_getch();
return 0;
}


The only solution (that's not working, unfortunately) I've got, is to create a const reference (
const std::vector<T> &
) and a regular reference (
td::vector<T> &
) inside a wrapper class, and bound them to the main vector in our wrapper class.

So, this is what I've done:

template <typename T>
class ConstLockVectorWrapper {
public:
ConstLockVectorWrapper(const std::vector<T> & vec)
: wrappedVector(vec), wrappedVectorRef(wrappedVector), wrappedVectorConstRef(wrappedVector), constLock(false)
{}

void lock()
{
if (constLock) // if the vector is already locked, we just exit the function
return;

// else we lock the vector
constLock = true;
}

void unlock()
{
if (!constLock) // if the vector is already unlocked (changable), we just exit the function
return;

// else we unlock the vector
constLock = false;
}

return_type get() // I need to return a const std::vector<T> & if constLock == true, and std::vector<T> & otherwise, what return type should I put in here?
{
if (constLock)
return wrappedVectorConstRef;
else
return wrappedVectorRef;
}

private:
bool constLock;
std::vector<T> wrappedVector;

// refs
std::vector<T> & wrappedVectorRef;
const std::vector<T> & wrappedVectorConstRef;
};


Of course, it doesn't work. Just because I don't know what to put in the return type of my
get()
fucntion.

I've tried using trailing return type, didn't work:

template <typename T>
class ConstLockVectorWrapper {
public:
// ...

private:
bool constLock;
std::vector<T> wrappedVector;

// refs
std::vector<T> & wrappedVectorRef;
const std::vector<T> & wrappedVectorConstRef;

public:
auto get() -> decltype((constLock ? wrappedVectorConstRef : wrappedVectorRef))
{
if (constLock)
return wrappedVectorConstRef;
else
return wrappedVectorRef;
}
};


I can't come up with any solution that will actually work, because I'm not so good at C++ yet.

So I'm asking for your help with my problem. Any suggestions or hints to solve this problem would be appreciated!

Thanks

PS



My main goal is to make my wrapper container-type-independent, so it can "lock" and "unlock" the const state of the container it's holding, independently of its type.

And here's the
print()
function I used in the first code snippet:

template <typename Container>
void print(const Container & c)
{
for (const auto & var : c)
std::cout << var << std::endl;
}

Answer

Fundamentally, a method always returns the same thing. The same type. Every time. It's not possible, in C++, to have a method sometimes return one type, and another type at other times. C++ does not work this way.

So, the initial approach would be to have get() return a proxy object with a state. Using, roughly, the same classes and names from your question:

class return_type {

      bool is_const;
      std::vector<T> &wrapped_vec;

public:
      return_type(bool is_constArg,
                  std::vector<T> &wrapped_vecArg)
          : is_const(is_constArg), wrapped_vec(wrapped_vecArg)
      {
      }

      void push_back(T &&t)
      {
           if (is_const)
                throw std::runtime_error(); // Or, whatever...
           wrapped_vec.push_back(std::forward<T>(t));
      }

      // return_type will have to implement, and baby-sit all other
      // methods you wish to invoke on the underlying vector.
};

return_type get()
{
    return return_type(constLock);
}

This is simple, but crude and somewhat tedious. You would have to implement every std::vector method you need to use in the return_type proxy.

A better approach would be to take advantage of C++11 lambdas. This will avoid the need to reimplement every wheel, at an expense of some additional code bloat. But, big deal. RAM is cheap, these days. Instead of get() and return_type, you will now be implementing two template methods in your wrapper: get_const() and get_mutable(). Each one of them takes a lambda parameter and invokes it and, if all goes well, passing it the wrapped vector as an argument:

      template<typename lambda>
      void get_mutable(lambda &&l)
      {
           if (constLock)
                throw std::runtime_error(); // Or, whatever...

           l(wrapped_vec);
      }

      template<typename lambda>
      void get_const(lambda &&l)
      {
           l(const_cast<const std::vector<T> &>(wrapped_vec));
      }

The only thing you now need to decide is whether you need access a mutable or a constant vector, and pick the right getter:

myWrapper.get_mutable( [&](std::vector<int> &v) { v.push_back(10); } );

get_mutable() throws an exception if the vector is locked at this time. Otherwise it passes the vector to your lambda. Your lambda does whatever the heck it wants with it, which can be push_back(), or anything else, then returns.

But if you only need read-only access to the vector, use get_const():

int s;

myWrapper.get_const( [&](const std::vector<int> &v) { s=v.size(); } );

Note that get_const() takes care to const_cast the vector, before invoking the lambda, so the lambda will not be able to modify it. This will be enforced at compile-time.

With some additional work, it would also be possible to clean this up a little bit, and have the getter also return whatever lambda returns to the caller, making it possible to do something like this:

int s=myWrapper.get_const( [&](const std::vector<int> &v) { return v.size(); } );

It's possible to have get_const() and get_mutable() be smart enough to figure out if the lambda returns something, and happily pass it back to the caller, whatever it is. And how to do that, I suppose, will have to be another question on stackoverflow.com

P.S. If you don't have C++11, you can just have get_const() and get_mutable() return the wrapped vector (with get_mutable() verifying that it's not locked). This really accomplishes the same thing. The key point is that due to the way that C++ works, you will have to disambiguate, in advance, whether you need constant or mutable access.

Comments