2

I have a class with a constructor which takes a non-const reference which is assigned to a member of the class. Now I want to create a const object of said class, but the constructor complains if I pass a const reference to the constructor. The code below which is a simplification of my original code demonstrates the problem.

As far as i can tell, there should be no problems with creating a const object since it is based on const data?

How can achieve what I'm trying to do in create_const_a?

#include <iostream>

class A {
private:
  double &d;
  const int &i;
public:
  A(double &dd, const int &ii)
    : d(dd), i(ii)
  {}

  void set_d(double a) {
    d = a;
  }

  void print() const {
    std::cout << d << " " << i << std::endl;
  }
};


A create_a(double &dd, const int &ii) {
  return A(dd,ii);
}

const A create_const_a(const double &dd, const int &ii) {
  return A(dd,ii);
}


void foo(A a)
{
  a.set_d(1.3);
  a.print();
}

void bar(const A a)
{
  a.print();
}


int main(int argc, char *argv[])
{
  double d = 5.1;
  int i = 13;

  foo(create_a(d,i));

  bar(create_const_a(d,i));

  return 0;
}

The error I get is:

test.cc: In function ‘const A create_const_a(const double&, const int&)’:
test.cc:27:17: error: binding ‘const double’ to reference of type ‘double&’ discards qualifiers
   return A(dd,ii);
                 ^
test.cc:8:3: note:   initializing argument 1 of ‘A::A(double&, const int&)’
   A(double &dd, const int &ii)
   ^

Update: After learning some new things about how const works with objects and non-const references within them, I eventually solved the original problem by introducing another type, say ConstA which only contains const references, which could then be used in each of the problematic cases.

kalj
  • 1,432
  • 2
  • 13
  • 30
  • 1
    You can go from non-const to const, but not vice-versa. The constructor needs a non-const, and your `create_const_a` receives a const. – AndyG Jan 18 '17 at 11:59
  • 5
    All those references... If you continue you will sooner or later come to a pointer where you have a dangling reference and bad things will happen. *Why* are you having references in the `A` class? What is the *actual* problem you are trying to solve with solution like this? – Some programmer dude Jan 18 '17 at 12:00
  • 1
    Your `create_const_a` method is returning a reference to a local object. This does not work the way you seem to think it does. See [this answer](http://stackoverflow.com/a/4643721/7359094) for more information. – François Andrieux Jan 18 '17 at 12:01
  • 2
    @FrançoisAndrieux No it returns a reference to a temporary object. Fortunately it's a `const` reference which prolongs the lifetime of the object until the end of the expression it is used in. Still not a good idea, but in this very specific case it will work. – Some programmer dude Jan 18 '17 at 12:06
  • Okay, returning a reference to a local variable is clearly invalid, but somehow returning a const reference seems to work fine. But that's a separate discussion and doesn't affect the present issue. I updated the question now to remove that. – kalj Jan 18 '17 at 12:07
  • @Someprogrammerdude I agree it looks messy, but this is used in some operator overloading / expression template magic where `A` corresponds to a temporary object used to refer to the actual objects being manipulated. In the real code, `d` and `i` are huge objects that I don't want to copy around. – kalj Jan 18 '17 at 12:09
  • @kalj: Changing the source without changing the error message is **bad** because they are now unrelated... – Serge Ballesta Jan 18 '17 at 12:17
  • @SergeBallesta I have ran the code through a compiler, and the message is exactly as in the question. – luk32 Jan 18 '17 at 12:23
  • @SergeBallesta Fixed. – kalj Jan 18 '17 at 12:30

3 Answers3

3

C++ prohibits this to avoid conversion of const reference to non-const.

Here is a small example of how this would happen:

struct foo {
    int& a;
    foo(int& b) : a(b) {}
    void bar() const {
        a = 5;
    }
};

The above compiles well, because a = 5 does not change the state of the foo object; it changes the state of an external int, so foo::bar is allowed to be const.

Now assume that we could do this:

const foo make_const(const int& x) {
    return foo(x); // Not allowed
}

Then we would be able to write

const foo f(make_const(10));
f.bar();

and modify a reference to a temporary int, which is undefined behavior.

Here is a small demo:

int x = 10;
cout << x << endl;
const foo f(x);
f.bar();
cout << x << endl;

It prints

10
5
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • The fact that the reference can be changed without violating const-correctness was completely new to me and explains a lot about why this seems impossible. – kalj Jan 18 '17 at 12:33
  • @kalj That's the trickiest part of `const`-ness when it applies to references and pointers. A pointer or a reference can be `const` while the item to which they point / reference would remain writable. – Sergey Kalinichenko Jan 18 '17 at 12:49
0

You are trying to drop constness:

const A create_const_a(const double &dd, const int &ii) {
  return A(dd,ii);
}

but dd is a non-const, so the created object A would not know that it cannot modify it's d member, while the caller of create_const_a would pass the variable believing so.

You cannot go around with out making a copy of dd, or dropping the const (e.g. through a const_cast) which is dangerous and strongly indicates an error in the design.

Your definition of A says "I need to be able to modify d", but in the create_const you promise that it won't be modified.

In other words, you should address the error in the design. Either release the constraint or make a copy. Making the "error go away" will just silence the symptom of bad design until you will get into real trouble when the breach of the contract ends up in repercussions.

luk32
  • 15,812
  • 38
  • 62
0

The function const A create_const_a takes as first argument a ref to a const double, and it then try to use it in ctor as a non const ref which is not allowed.

You should remove the const qualifier here.

If you are really sure of why you can do it here, that means if you can be sure that create_const will allways receive non const parameters, you can explicitely use const_cast to remove the qualifier:

const A create_const_a(const double &dd, const int &ii) {
  return A(const_cast<double &>(dd),ii);
}

But BEWARE, this will lead to Undefined Behaviour if you do that on a variable that was initially declared as const.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252