Is this valid c++

This compiles under g++, but I'm not sure if it's just letting me get away with it.

Y::x is a reference to an X. I can alter x through a constant member function of Y. Is this generally allowed?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct X
{
	int val;
	void func() {
	}
};

struct Y
{
	X &x;
	Y(X &x) : x(x) {}
	void func() const {
		x.val=3;
		x.func();
	}
};

void test() {
	X x;
	Y y(x);
	y.func();
}
Yes.

Many programmers don't fully understand what it means to be a "const" member function. Given this class:

1
2
3
4
5
6
7
8
9
10
11
class Foo {
   int x;
   std::string& s;
   std::vector<float>* v;

  public:
    Foo( std::string& s ) : x( 42 ), s( s ), v( new std::vector<float>() ) {}

    void f();
    void g() const;
};


When you call a const member function, the "this" pointer becomes const. This is well known. But in practice, what that means is that Foo's data members are now effectively this:

1
2
3
4
5
class Foo {
   int const x ;
   std::string& const s;
   std::vector<float>* const v;
};


Notice where the "const" is. So, reading the declarations: s is now a constant reference to a std::string, and v is now a constant pointer to a vector. These declarations basically mean that the reference is constant, not what the reference refers to, and the pointer is constant, not the thing that the pointer points to.

In the reference case, the const is irrelevant since references can't be rebound anyway. In the pointer case, it means you can't change the pointer value (you can't make v point to a different vector), but you can change the vector that v points to.
Last edited on
Thankyou. That has saved me from having to completely refactor a library.

It does make sense as the object itsself is not changed when you alter the value pointed to by the reference. But somewhere along my path of learning I got it into my head that it's not allowed.
I had that misconcept myself up until a few years ago.

I don't create objects that contain references because I prefer to retain value semantics on all my objects, and reference data members leave me with objects that are copyable but have very weird (usually incorrect) assignment semantics.
The justification in this case is the following.

The library is responsible for loading and validating a complicated data set and has the following initialization call:

1
2
bool libprob_initialise(const std::string &casename,
 const ProbIO &io, PIOptions opts = PIOptions());


ProbIO and the derived class I'm using look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class ProbIO
{
public:
    typedef enum {if_avails, if_cci, if_ctawr, if_cvi, if_or,
     if_qr, if_visits } InputFile;

protected:
    virtual boost::shared_ptr<std::istream> inputFileStream(InputFile f) const =0;
public:
    boost::shared_ptr<std::istream> inputFile(InputFile f) const;
    virtual void lp_logError(const std::string &error) const =0;
    virtual void lp_logInformation(const std::string &info) const =0;
};

template<class Input_, class Output_, class Conf_>
class ProblemIO_SepConf : public ProbIO
{
private:
    Output_ o;
    Input_ i;
    Conf_ c;
protected:
    virtual boost::shared_ptr<std::istream> inputFileStream(InputFile f) const {
        if(f==if_or || f==if_qr || f==if_ctawr) {
            return c.inputFileStream(f);
        } else {
            return i.inputFileStream(f);
        }
    }
public:
    ProblemIO_SepConf(const Input_ &i, const Output_ &o,
     const Conf_ &c) : i(i), o(o), c(c) {}

    virtual void lp_logError(const std::string &error) const {
        o.lp_logError(error);
    }
    virtual void lp_logInformation(const std::string &info) const {
        o.lp_logInformation(info);
    }
};


Inside the initialization call, I pass const ProbIO references around all over the place. The two lp_log functions generally open files, print to stdout/stderr, or ignore the message. So there was no reason for them to be non-const. Until of course, 11 months later, the unforseen situation where I have to capture the log in a string has occurred.

The following class, with reference members, seems to be the perfect solution.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ProbOutputString
{
private:
    std::string &err;
    std::string &log;
public:
    ProbOutputString(std::string &err, std::string &log) :
     err(err), log(log) {}

    void lp_logError(const std::string &error) const {
        err += error;
    }
    void lp_logInformation(const std::string &info) const {
        log += info;
    }
};
Topic archived. No new replies allowed.