If class A modifies its construction parameters, can I initialize const A's with const parameters?
Asked Answered
D

5

0

Suppose I have

class A final { 
    int& ir; 
public:
    A(int& x) : ir(x) { }
    void set(int y) { ir = y; } // non-const method!
    int get() const { return ir; }
};

and

const int i;

Obviously I can't have

 A a(i);

since that would breaks constness. But I also cannot have

 const A a(i);

despite the fact that this will not break constness de-facto. C++ doesn't support "const-only" ctors, e.g. in this case one which would take a const int&. Is there a way to get const A a wrapping a reference to i - other than

A a(const_cast<int &>(i))

?

Dromous answered 21/4, 2016 at 14:57 Comment(15)
Could you please describe what you are trying to achieve?Gravettian
"despite not breaking constness"? I don't understand. You can afterwards write to a.ir. I would say that's pretty unsafe.Masoretic
What comes to mind is class A { variant<int&, int const&> v; A(const int&a):a(a) { } A(int &a):a(a) { } };. not sure whether this works as-is, but could be a start. Then visit a.v with a [](auto &&a){}Masoretic
@JohannesSchaub-litb: Edited in the hopes of being clearer. Note ir is private. Your suggestion is interesting and I'm thinking about it.Dromous
@paceholder: See edit. A is a sort of a facade for an int (in reality I have a more complex facade for something else). I should be able to construct a facade which can mutate its backing int, if that int is non-const, but also construct the "same" facade, const, with a const int backing it. It seems like C++ is almost forcing me to choose either or.Dromous
Instead of const A, you can settle for A<const_> or A<>. And modify ir adequately depending on the template parameter.Masoretic
I can't imagine set() function in the const case.Gravettian
Note that this is pretty much the same problem as with iterators and const_iterators. There are workarounds, but no language feature to solve that problem.Barber
@Quentin: I think I see what you mean, but if you could link someplace discussing this it would be nice.Dromous
Isn't this what factories are for?Vicennial
@LightnessRacesinOrbit: No, unless I misunderstand what you mean.Dromous
@einpoklum: What else are they for then?Vicennial
@LightnessRacesinOrbit: Factories are for creating objects, in general. But - factories have to be coded too, they don't work magically. The solution to this problem could theoretically be placed inside a factory but the question remains of how to construct a const A with a const int&, factory or no.Dromous
@einpoklum: The factory would construct an A, use whatever mutators are needed to fully configure it, then give you it as a const (be it via copying/moving, or reference, or whatever). I thought that was fairly typical usage of factories.Vicennial
@LightnessRacesinOrbit: Great, just write an answer explaining how the factory would construct an A when it is given a const int &....Dromous
M
4

"Const" means that the object is constant during between the constructor end and destructor begin (during construction, you must be able to change the object). C++ doesn't have a "preparation constructor" of sorts that is preferred for const objects.

You can try this workaround by applying boost::variant. This is not completely type-safe at compile time, but detect errors at runtime by throwing an exception at least.

#include <boost/variant.hpp>
#include <iostream>

class R {
  boost::variant<int&, const int&> v;
public:
  R(int&v):v(v) { }
  R(const int&v):v(v) { }

  // works with both
  int get() const { 
     return boost::apply_visitor( 
        [](int x){return x;}, v); 
  }

  // only works for non-const A. If at construction, a const
  // int was passed, throws an error at runtime
  void set(int x) {
     boost::get<int&>(v) = x;
  }
};


int main() {
  int a = 0;
  const int b = a;
  R r1(a);
  R r2(b);
  const R r3(a);
  std::cout << r1.get() << r2.get() << r3.get();
  // r3.set(1); // compile error
  r1.set(1); // works
  r2.set(1); // runtime error
}
Masoretic answered 21/4, 2016 at 15:27 Comment(2)
1. Isn't variant in the standard already? 2. How slow is apply_visitor?Dromous
@Dromous I believe variant is in C++17 (I could be wrong). Definitely not C++11 or C++14. apply_visitor is a switch statement basically (perhaps a bit more fancy in the boost impl, I have no idea). But since both switch cases here end up executing the same code (simply a move of an int), I would expect the compiler to fold them and inline the entire apply_visitor. If in doubt, ignore until it causes performance problems :)Masoretic
F
3

C++ doesn't have a concept of fully constant classes, and compiler may not perform if reference int & is used only as const int &. So basically, you cant do it.

Fayum answered 21/4, 2016 at 15:22 Comment(0)
P
1

It's weird but

class A final {
    union{
    int& ir;
    const int &cir;
    };
public:
    A(int& x) : ir(x) { }
    A(const int& x) : cir(x) { }
    void set(int y) { ir = y; } // non-const method!
    int get() const { return ir; }
};

int main(int argc, char *argv[])
{
    const int cv = 8;
    int v = 6;
    A a( cv );
    std::cout << a.get() << std::endl;
    a.set( v );
    std::cout << a.get() << std::endl;
    return 0; 
}

Also your set and get method opearates on values not references, so it's looks like you a doing something wrong

Poulter answered 21/4, 2016 at 15:21 Comment(5)
I would almost upvote this for it's simplicity, if only the standard allowed reference data members inside unions.Prang
@user2079303 this also wouldn't really work: His example would compile and produce undefined behavior, since nothing stops him from calling a.set(v). You need to remember what constructor you took, by for example using a boost::variant or storing it manually.Masoretic
@JohannesSchaub-litb good point. It seems that the simplicity is not worth it in this case.Prang
@JohannesSchaub-litb could you explain what kind of undefinite behavior you a talking about?Poulter
@jeka modifying a "const int" is causes undefined behavior.Masoretic
G
1

This is not how I would write the code but it somehow works. If you construct a template class from the const variable, the set() function is defined also const and does not change the state. By constructing from non-const variable, the set function is able to change the state.

#include <iostream>
#include <string>
#include <type_traits>

template<typename T>
class A
{
    T &_i;
public:
    A(T &i) : _i(i) {}

   template <typename D = T,
             typename = std::enable_if_t<std::is_const<D>::value
                                        >
            >
    void set(T i) const { std::cout << "Do nothing on set" << std::endl; }

   template <typename D = T,
             typename = std::enable_if_t<!std::is_const<D>::value>
            >    
    void set(T i) { std::cout << "Do something on set" << std::endl; }
};

int main()
{
    {
        std::cout << "Construct from NON const variable" << std::endl;
        int b = 5;

        A<decltype(b)> a(b);   

        a.set(3);
    }

   {
        std::cout << "Construct from const variable" << std::endl;
        int const  b = 5;

        A<decltype(b)> a(b);      

        a.set(3);
    }
}

This prints:

Construct from NON const variable
Do something on set
Construct from const variable
Do nothing on set
Gravettian answered 21/4, 2016 at 16:6 Comment(0)
C
0
class A { 
    const int& ir; //  <<-- it's a const int. 
    /* ... */
public:
    A(int& x) : ir(x) { }
};
Clarendon answered 21/4, 2016 at 15:0 Comment(1)
Not what I meant, I want to have some non-const A's and some const A's, and to be able to construct the const A's using const int&'sDromous

© 2022 - 2024 — McMap. All rights reserved.