3

Consider the following minimal example.

#include <vector>

class Data
{
    std::vector<int>& v;
public:
    Data(std::vector<int>& _v) : v(_v) {}
    Data(const std::vector<int>& _v) : v(_v) {} // error!
};

int main()
{
    std::vector<int> v;
    const std::vector<int> c_v;
    Data d(v);
    Data const_d(c_v);

    return 0;
}

This does not compile. The whole output from g++ -Wall below.

const.cpp: In constructor ‘Data::Data(const std::vector<int>&)’:
const.cpp:8:41: error: invalid initialization of reference of type ‘std::vector<int>&’ from expression of type ‘const std::vector<int>’

The reason is clear to me: The const keyword is upset about my cast in line 8. The issue: I really sometimes need the Data class with std::vector, but sometimes with const std::vector. Like having two classes: One for reading into Data, and one for reading from Data. However, I do not like to write two Data classes with almost redundant functions. My questions:

  • Can you give a nice solution to how I can achieve what I try to do in main()? C++11 solutions are pretty welcome, too :)
  • Is this the reason for iterator and const_iterator?
Johannes
  • 2,901
  • 5
  • 30
  • 50

2 Answers2

2

const-ness of data members needs to be known at compile time. If you "sometimes" need a reference to a const vector and sometimes non-const, you should create a class hierarchy with the base abstract class containing the common functionality, and inherit it in two Data classes: Data and ConstData. The Data would contain a non-const vector, and ConstData would contain a const vector. This way you would not duplicate any logic, while two separate classes would contain two references of different const-ness.

Here is an example:

class AbstractData {
public:
    // Common functions use vect() and const_vect()
    void common_function1();
    void common_function2();
protected:
    virtual vector<int>& vect() const = 0;
    virtual const vector<int>& const_vect() const = 0;
};

class Data : public AbstractData {
    vector<int>& v;
public:
    Data(vector<int>& _v) : v(_v) {}
protected:
    vector<int>& vect() const {
        return v;
    }
    const vector<int>& const_vect() const {
        return v;
    }
};

class ConstData : public AbstractData {
    const vector<int>& v;
    vector<int> temp;
public:
    ConstData(const vector<int>& _v) : v(_v) {}
protected:
    vector<int>& vect() const {
        return temp; // You can choose to throw an exception instead
    }
    const vector<int>& const_vect() const {
        return v;
    }
};

Note that this class hierarchy may need a destructor and various copy constructors, depending on the usage.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Nice reasoning! So my idea with using template wasn't so bad? Probably a good exercise to use CRTP here instead of virtual functions? :) – Johannes Oct 27 '12 at 19:02
  • @Johannes One problem with a template-based solution is that you wouldn't be able to easily differentiate between the const type `T` vs. the non-const one. If some functions of your template would need to access the vector in a non-const way, the entire template would fail to compile. A template-based solution would inherit a "mutable" data template class from the "immutable" data template class, and add mutating functions to it. – Sergey Kalinichenko Oct 27 '12 at 19:09
  • not 100 percent sure if I got that. If I have multiple objets to store, I'd use a typelist or something similar. Shouldn't this set the type at compile time? – Johannes Oct 27 '12 at 19:16
  • @Johannes Yes, that will set the type at compile time. The issue is, if the type happens to be `const`, and some of your template's methods require non-const access, these methods will not compile. – Sergey Kalinichenko Oct 27 '12 at 19:19
1

The error you got was along the lines of:

foo.cpp: In constructor ‘Data::Data(const std::vector<int>&)’:
foo.cpp:8:44: error: invalid initialization of reference of type ‘std::vector<int>&’ from expression of type ‘const std::vector<int>’

...because you are trying to create a non-const reference to data that you (Data) promised your caller would be treated as const.

Here is a solution which declares a const reference as Data's member:

#include <vector>

class Data
{
    const std::vector<int> &v;
public:
    Data(std::vector<int>& _v) : v(_v) {}
    Data(const std::vector<int>& _v) : v(_v) {} // error!
};

int main()
{
    std::vector<int> v;
    std::vector<int> c_v;
    Data d(v);
    Data const_d(c_v);

    return 0;
}

Another option is to use a non-reference member instead:

#include <vector>

class Data
{
    std::vector<int> v;
public:
    Data(std::vector<int>& _v) : v(_v) {}
    Data(const std::vector<int>& _v) : v(_v) {} // error!
};
Brian Cain
  • 14,403
  • 3
  • 50
  • 88