views:

618

answers:

12

While striving for const-correctness, I often find myself writing code such as this

class Bar;

class Foo {
public:
  const Bar* bar() const { /* code that gets a Bar somewhere */ }

  Bar* bar() {
    return const_cast< Bar* >(
      static_cast< const Foo* >(this)->bar());
  }
};

for lots of methods like bar(). Writing these non-const methods which call the const ones by hand is tedious; besides, I feel I am repeating myself – which makes me feel bad.

What can I do to alleviate this task? (Macros and code generators are not allowed.)

Edit: Besides litb's solution I also like my own. :)

+3  A: 

You can do something like this:

class Bar;

class Foo {
public:
  const Bar* bar() const { return getBar(); }

  Bar* bar() {
   return getBar();
  }

  private:
    Bar* getBar() const {/* Actual code */ return NULL;}
};
Naveen
So you end up writing three functions instead of two? Not a great solution, IMHO.
anon
I was trying to avoid repeating the code or to use the const_cast. The first two functions does nothing but call the inner helper function.
Naveen
@Neil: There are two conflicting requirements here. It's desirable that the const member return a const pointer. The result of "code that does something" needs to be modifyable so it should return a non const pointer. To catch both of these requirements, the simplest option is to have the 3rd helper function.
Richard Corden
@Neil: removing code duplication is usually solved by introducing new function with common code, isn't it?
Tadeusz Kopec
`Bar* getBar() const` could not be compiled if it returns pointer to member variable.
Kirill V. Lyadvinsky
@JIa3ep: I agree that it won't compile. But it should not be const method in the first place if that is the case. So I assumed that it is returning a copy of the pointer.
Naveen
OP said somewhere in comments: "No, sometimes it returns a member<...>"
Kirill V. Lyadvinsky
Compiles just fine on mine...
Chris Huang-Leaver
A: 

http://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/7b5432936bf5467d#

Abhay
The OP didn't want to use macros.
Martin B
There are other techniques mentioned too (templates)
Abhay
A template function "toconst()" is used, but the macro is a required part of the solution described there.
Martin B
O.K. But what abt other solutions; the 'mutable' (IMHO its an elegant solution to this problem) and the post by SG at the end. I mean this is a classical problem discussed over and over on c.l.c++.* forums and offlate people seem to agree on 'mutable' being simple, elegant and correct from SRP pattern point-of-view.
Abhay
+1  A: 

I've also felt this pain before -- in essence, you're trying to tell the compiler that constness "propagates" through bar(). Unfortunately, as far as I'm aware, there is no way to do this automatically... you'll just have to write the second version of the function by hand.

Martin B
+2  A: 

My personal feeling is that if you are doing this a lot, there is something a bit suspect in your design. On the occasions I have had to do something similar, I've usually made the thing being accessed by the methods mutable.

anon
Is there anything suspect in vector's 'iter begin()' and 'const_iter begin() const'? If it didn't have both there would be a problem.
Tadeusz Kopec
+1 for the look at redesign, -1 for the making variables mutable
Grant Peters
mutable is sometimes necessary - I'm not suggesting it as a cure-all
anon
@tkopec the two begin() functions actually return completely different types, which is not the case in the OP's code
anon
I agree with Neil here that 'mutable' is an elegant solution to this problem. There will not be an issue of code duplication in this case at all. What if a class has multiple getters / setters with const-nonconst pairs required for each? How many common functions would one need to churn-out?
Abhay
A pseudo-example would be a `class Car` which has `tires`. I want to be able to `myConstCar.tires().spare().isInflated()` and `myCar.tires().spare().inflate()`. I want to avoid adding `Car::isSpareTireInflated()` and `Car::inflateSpareTire()` since it leads to an explosion of the number of methods of `Car`.
Tobias
`mutable` where it's needed is a good thing. I always make things that don't change the value of an object `mutable` - even if i don't need to change it by const member functions.
Johannes Schaub - litb
+4  A: 
Alexey Malistov
-1. If you invoke 'bar() const' on a **really** const object, it will invoke 'This() const', try to remove constness and you will have undefined behaviour.
Tadeusz Kopec
OP's question implies that non-const function bar does not change anything.
Alexey Malistov
OK, sorry. I thought const_cast is enough to cause UB, but standard says that UB is after modification.
Tadeusz Kopec
+1 for second solution. const_cast is pretty safe in this case, because it will be applied to initially non-const object.
Kirill V. Lyadvinsky
It's down to the programmer to figure out whether the code invalidly modifies objects or not. If that's acceptable, then why "strive for const-correctness" to start with, and then give up at this point?
Steve Jessop
The first one is still a dangerous thing. A function should be safe in isolation, independent of other overload. This solution makes the const version unsafe, because it depends on the fact that the non-const version doesn't write to the object. I recommend: always use the second version.
Johannes Schaub - litb
The second one relies on on the fact that the const version returns a modifiable object when "this" is modifiable. Mutatis mutandem, it's not "safe" either. Really it's down to how close to safe you think is safe enough, which depends on the complexity of the code whose unchecked const behaviour you're relying on.
Steve Jessop
A: 

Your code implies that the const bar() is actually creating and returning a new Bar, and I find that peculiar if you're doing that lot.

For me a bigger problem is that constness in a member function is limited to reference and non-pointer members. The moment you have (non-const) pointer members, a const function can change them even though it claims to be const on the box.

If your Bar instance is a member variable, consider returning a reference instead.

Will
No, sometimes it returns a member, sometimes it queries another object for the required member. For the reference vs. pointer issue: IMHO this is a matter of style, and it does not change the problem.
Tobias
A: 
Notinlist
Thanks for providing a concrete example. Apart from that, could elaborate how your answer relates to my question?
Tobias
A: 

Assuming you accept const-correctness as a technique, then I think that means you prefer compiler-checked const-correctness to brevity. So you want the compiler to check two things:

  1. That when "this" is non-const, the referand of the pointer you return can safely be modified by the caller.
  2. That when "this" is const, whatever work you do does not perform any non-const operations on "this".

If the const version calls the non-const then you don't get (2). If the non-const version calls the const one and const_casts the result, then you don't get (1). For instance suppose Bar is actually char, and the code you write ends up returning (in some cases) a string literal. This will compile (and -Wwrite-strings gives no warning), but your caller ends up with a non-const pointer to a string literal. This contradicts "you prefer compiler-checked const-correctness".

If they both call a helper member function Bar *getBar() const, then you get both (1) and (2). But if it's possible to write that helper function, then why are you messing about with const and non-const versions in the first place, when it's perfectly OK to modify the Bar returned from a const Foo? Occasionally perhaps some detail of implementation means you're implementing an interface with two accessors even though you only need the one. Otherwise either the helper can't be written, or else the two functions can be replaced just by the single helper.

As long as code size is not a concern, I think the best way to achieve both (1) and (2) is to have the compiler actually consider both cases:

struct Bar { int a; };
struct Foo {
          Bar *bar()       { return getBar<Bar>(this); }
    const Bar *bar() const { return getBar<const Bar>(this); }

    Bar *bar2() const { return getBar<Bar>(this); } // doesn't compile. Good.
    Bar *bar3() const { return getBar<const Bar>(this); } // likewise

    private:
    template <typename B, typename F>
    static B *getBar(F *self) {
        // non-trivial code, which can safely call other functions with 
        // const/non-const overloads, and we don't have to manually figure out
        // whether it's safe to const_cast the result.
        return &self->myBar;
    }
    Bar myBar;
};

If the code is trivial, like an operator[] which accesses some array owned by the object, then I would just duplicate the code. At some point, the above function template is less coding effort than the duplication, at which point use the template.

I think the const_cast approach, while clever and seemingly standard, just isn't helpful, because it chooses brevity over compiler-checked const-correctness. If the code in the method is trivial, then you can duplicate it. If it's not trivial, then it's not easy for you or a code maintainer to see that the const_cast is actually valid.

Steve Jessop
A: 

Does the const-ness of Bar affect the const-ness of your Foo? Or are you just doing that because there's no way to distinguish the const Bar* vs Bar* versions? If this latter is the case, I'd just have one bar() that returned a Bar* and be done with it. Afterall, something that takes a const Bar* can take a Bar* just fine, anyway. Don't forget, the const-ness of the bar() methods is about Foo, not about Bar.

Also, if you offer both const- and non-const Bar* and are happy to have Foo be non-const, willy-nilly, then the constness of your Foo (and thus the bar() methods) obviously isn't that important, you're just filling in const and non-const versions for the heck of it. In which case, if you allow a non-const version, again just offer /only/ that one, consumers of const will happily take a non-const. Just, the way you've got const and non-const going there, it looks like it doesn't make much difference to the program.

If you want const Foo objects to be able to return both const and non-const Bar*s, then just make bar() const and return a non-const Bar*.

I don't know, I'd have to see more to give a real answer.

Think through though, which const-nesses are truly connected, and whether your program needs both const Foos and non-const Foos. Allowing all possibilities willy-nilly, it seems like you haven't thought through what the program requires.

JDonner
I totally disagree. The reasoning is this: If `this` Foo is const, then I cannot return a reference to a non-const Bar-member (which can then indirectly modify its parent). If `this` Foo is non-const, however, then I am free to return a non-const Bar-member.
Tobias
Nope, Bar /is not a member/. See his comment, '...code that gets a Bar somewhere..'. So it doesn't affect Foo's const-ness.
JDonner
... oh, you're the OP. Well, give more code and make it clearer then.
JDonner
+4  A: 

Another way could be to write a template that calls the function (using CRTP) and inherit from it.

template<typename D>
struct const_forward {
protected:
  // forbid deletion through a base-class ptr
  ~const_forward() { }

  template<typename R, R const*(D::*pf)()const>
  R *use_const() {
    return const_cast<R *>( (static_cast<D const*>(this)->*pf)() );
  }

  template<typename R, R const&(D::*pf)()const>
  R &use_const() {
    return const_cast<R &>( (static_cast<D const*>(this)->*pf)() );
  }
};

class Bar;

class Foo : public const_forward<Foo> {
public:
  const Bar* bar() const { /* code that gets a Bar somewhere */ }
  Bar* bar() { return use_const<Bar, &Foo::bar>(); }
};

Note that the call has no performance lost: Since the member pointer is passed as a template parameter, the call can be inlined as usual.

Johannes Schaub - litb
Interesting, that's similar to the solution I came up with myself after bending my mind quite a bit :) Two comments: 1. The fact that every class needs to inherit from const_forward is somewhat unfortunate (and I personally would prefer protected inheritance here). 2. Have you checked this on msvc? I have had quite a few issues with msvc determining the correct member function pointer if the signatures only differ by const. Still, IMHO the best solution so far!
Tobias
Unfortunately, if you inherit protected, then you have to make the base a friend of the derived class, in order to make the base "see" the inheritance, *or* you use a C-Style cast to cast from the base `this` to the derived `this` (a c-style cast will still be defined - even when multiple inheritance is involved, and will not care about visibility). But i considered a c-style cast to be too dangerous. I tested this on comeau and GCC :)
Johannes Schaub - litb
You could use containment and make "const_forward" a member. But then you need to pass the this pointer at some point, or at all calls to "use_const". I didn't like that :)
Johannes Schaub - litb
D'oh! I didn't know that the standard would only allow C-style downcasts from protected base classes. It's again one of those C++-"features" which make me cry "Why Bjarne?!" Well, gcc and msvc happily ignore this mis-feature.
Tobias
No, it allows C-style casts to casts from public bases too, of course. But unlike `static_cast`, c-style casts allow casting from protected/private bases and to protected/private bases. And unlike `reinrerpret_cast`, the cast using a c-style cast will be perfectly valid and address adjustments are done when necessary.
Johannes Schaub - litb
I don't like solutions requiring to pass a `this` pointer, either :) What I like about your solution is that you pass the pointer as a template argument. What I don't like (and what I prefer in my solution :) is that this means you have to write the types of all parameters again at the call site. I have tried to think about this, but I could not find a way to only specify the method as a template parameter, but not the types. Do you have an idea of how this might be done?
Tobias
Well if you can use a macro and have a `__typeof__` or `decltype` or similar c++0x/extension available, you could do `#define FN(X) decltype(getC( and further overloads for every other parameter count... . But i don't know of a more easy way to solve this. Especially i don't know of a macro-free and still terse way. And i don't know any way to do it without a `decltype`/`typeof`
Johannes Schaub - litb
Well, the issue with a function template like getC is that msvc has a bug that they cannot do overload resolution in this case if the overload just differs by cv-type, and which they claim is "impossible to fix." See my solution then, that's where I discovered this :(
Tobias
+1  A: 

FYI - The code posted by the OP is the preferred method given in Scott Meyers' "Effective C++ - Third Edition". See Item #3.

mocj
A: 

The answer I came up with myself after bending my mind a bit. However, I think I can improve it using the ideas from litb's answer, which I'll post later. So my solution so far looks like this:

class ConstOverloadAdapter {
protected:

  // methods returning pointers 

  template< 
    typename R, 
    typename T >
  R* ConstOverload(
    const R* (T::*mf)(void) const) {
      return const_cast< R* >(
        (static_cast< const T* >(this)->*mf)());
    }

  // and similar templates for methods with parameters 
  // and/or returning references or void
};

class Bar;

class Foo : public ConstOverloadAdapter {
public:
  const Bar* bar() const { 
    /* implementation */ }

  Bar* bar(void* = 0) {  // the dummy void* is only needed for msvc
                         // since it cannot distinguish method overloads
                         // based on cv-type. Not needed for gcc or comeau.
    return ConstOverload(&Foo::bar); }
};
Tobias