7

// Cat.h

class Cat
{public:
    void const_meow() const{ ... };
    void meow(){ ... };
};

class CatLibrary
{public:
    std::vector<std::shared_ptr<Cat>>::iterator begin()
    { 
        return m_cat_list.begin();
    }

    // compile error, the compiler complains cannot covert type
    //     from `std::vector<std::shared_ptr<Cat>>::const_iterator` 
    //     to   `std::vector<std::shared_ptr<const Cat>>::const_iterator`
    std::vector<std::shared_ptr<const Cat>>::const_iterator begin() const 
    { 
        return m_cat_list.cbegin();
    }
private:
    std::vector<std::shared_ptr<Cat>> m_cat_list;
};

// main.cpp

CatLibrary cat_library;       
cat_library.add(std::make_shared<Cat>());
cat_library.add(std::make_shared<Cat>());

for(auto& cat: cat_library )
{
   cat->const_meow(); 
   cat->meow(); 
}       
for(const auto& cat: cat_library)
{
   cat->const_meow(); 
   cat->meow();       // hope to compile error due to invoking non_const method of Cat.
}


const CatLibrary& const_cat_library = cat_library;        
for(auto& cat: const_cat_library ) 
{
   cat->const_meow(); 
   cat->meow();       // hope to compile error due to invoking non_const method of Cat.
}
const CatLibrary& const_cat_library = cat_library; 

for(const auto& cat: const_cat_library ) 
{
   cat->const_meow(); 
   cat->meow();       // hope to compile error due to invoking non_const method of Cat.
}

I want my CatLibrary expose the non-const begin() and non-const end() in which a client can iterate the smart pointer pointing to the mutable Cat. And the const begin() and const end() return the iterator which points the immutable one.

Then when the client iterates the const CatLibrary, I would not worry he could modify the content of Cat in library.

But the const added to my member function begin() only qualifies the pointer to be a const pointer, not the Cat it points.

Without the pointer involved, the vector with constness makes the iterator point to a element with constness also. But I want this effect also applies the element pointed by smart pointer.

I have a approach to solve my problem, but I am not sure what problems would happen in future use.

Maintain two vectors in const and nonconst

#include <iostream>
#include <memory>
#include <vector>

class Cat
{public:
    void const_meow() const { std::cout << "meow" << std::endl;}
    void meow() { std::cout << "meow" << std::endl;}
};


class CatLibrary
{public:

    void add(std::shared_ptr<Cat> cat)
    {
        m_cat_list.push_back(cat);
        m_cat_const_list.push_back(cat);
    };

    std::vector<std::shared_ptr<Cat>>::const_iterator begin()
    { 
        return m_cat_list.begin();
    }

    std::vector<std::shared_ptr<const Cat>>::const_iterator begin() const
    { 
        return m_cat_const_list.begin();
    }

    std::vector<std::shared_ptr<Cat>>::const_iterator end()
    { 
        return m_cat_list.end();
    }

    std::vector<std::shared_ptr<const Cat>>::const_iterator end() const
    { 
        return m_cat_const_list.end();
    }


private:
    std::vector<std::shared_ptr<Cat>> m_cat_list;
    std::vector<std::shared_ptr<const Cat>> m_cat_const_list;
};


int main()
{
   CatLibrary cat_library;

   cat_library.add(std::make_shared<Cat>());
   cat_library.add(std::make_shared<Cat>());
   cat_library.add(std::make_shared<Cat>());

   const CatLibrary& const_cat_library = cat_library;
   for(auto& cat: cat_library)
   {
      cat->meow();
   }

   return 0;
}

Or is there another solution to solve this kind of constness problem on smart pointer in vector?

Chen OT
  • 3,486
  • 2
  • 24
  • 46
  • 3
    I think I'd look to use something like boost's transform_iterator to do the conversion during iteration instead of maintaining two vectors. – Flexo Aug 06 '14 at 06:27

3 Answers3

1

In the example you posted there is no need for the const versions of begin() and end(). You can use a const reference in the ranged based for loop without having const versions of those functions and you don't need the cat to be const auto& at all to call the const member function.

There may be the need of const begin() and end() if your cat_library object itself would be const, but then you couldn't add items like that.

nh_
  • 2,211
  • 14
  • 24
  • But if I have a read only function like `void dump_cat(const CatLibrary& cat_library){ for(auto& cat : cat_library){...}; }`, then the cat_library I passed in would be a const variable. – Chen OT Aug 06 '14 at 08:01
  • Yes thats right, then you would indeed need const begin() and end(). You can use this: std::vector>::const_iterator begin() const { return m_cat_list.begin(); } to allow the iteration, although the cat object itself is still non-const and may be modified, but I cannot think of an easy way to prevent this right now. – nh_ Aug 06 '14 at 08:09
  • Yeah. Without the pointer involved, the vector with constness would make iterator point to a element with constness also. But I want this effect also applies the element pointed by smart pointer. Maybe I would check the `transform_iterator ` as Flexo said. Thanks. – Chen OT Aug 06 '14 at 08:20
0

I'd actually consider turning your abstraction around and encapsulating your collection of cats. Iterators and mutable objects are implementation details.

So write the functions in cat instead:

PrintCats()
PlayWithCats()

If your cats library isn't aware of the operations you want to perform on them, you can look at passing in function pointers. boost::function is good for this. You'd have a function like

void CatLibrary::DoStuffToCats(boost::function<void, (const Cat&)> f))
{
    std::foreach(m_cat_list.begin(), m_cat_list.end(), f);
}
DanDan
  • 10,462
  • 8
  • 53
  • 69
0

I played a little with boost::transform_iterator and it seems possible to achieve what you want, altough I find the result not satisfying. I guess DanDan suggestion to not expose implementation details to the user might be the right way to go.

Nevertheless here is the my shot for boost::transform_iterator as a reference:

#include <boost/iterator/transform_iterator.hpp>

class CatLibrary
{
public:
  typedef std::vector<std::shared_ptr<Cat>> cat_list_t;
  typedef std::function<const Cat *(const std::shared_ptr<Cat>&)> transform_t;
  typedef boost::transform_iterator<transform_t,
                                    cat_list_t::const_iterator> const_iterator;  

[...]

  const_iterator begin() const {
    return const_iterator(std::begin(m_cat_list),
                          [](const std::shared_ptr<Cat>& c) {
                            return static_cast<const Cat *>(c.get());
                          });
  }
  const_iterator end() const { // same as above ... }
};

The type of cat inside the for loops using the const_cat_library is now const Cat* and therefore calling non-const functions is not allowed. Compiling with g++ -c --std=c++11 transform_iterator.cxx -Wall -I$BOOSTINC (gcc version 4.8.1) yields the following errors:

error: invalid initialization of non-const reference of type 'const Cat*&' from an rvalue of type 'boost::[...]::reference {aka const Cat*}'
    for(auto& cat: const_cat_library ) {
                   ^
//twice:
error: passing 'const Cat' as 'this' argument of 'void Cat::meow()' discards qualifiers [-fpermissive]
    cat->meow();       // hope to compile error due to invoking non_const method of Cat.
              ^

One of the many problems that may arise is that the shared_ptr is circumvented and a user can delete the Cat object inside the loop.

vscharf
  • 893
  • 1
  • 7
  • 5