12

Is there any advantage using one over the other:

class Foo
{
public:
    const int& get() const
    {
        // stuff here
        return myInt;
    }

    int& get()
    {
        return const_cast<int&>(static_cast<const Foo*>(this)->get());
    }
};

Or

class Foo
{
public:
    int& get()
    {
        // stuff here
        return myInt;
    }

    const int& get() const
    {
        return const_cast<Foo*>(this)->get();
    }
};

I only used the first one, but I just saw the second one used somewhere, so I am wondering.

The comment // stuff here could be a non-trivial check like retrieving the index of a table in order to return a ref on a member of the table (for example: myInt = myTable[myComputedIndex];) so I cannot just make it public. Thus table and any member are not const.

Rakete1111
  • 42,521
  • 11
  • 108
  • 141
Silouane
  • 324
  • 3
  • 13

6 Answers6

20

If you have to make a function that is const-agnostic, and avoids duplication, one neat way to do it is delegating implementation to a template, for example

class Foo {
private: 

    int my_int;
    template <typename ThisPtr>
    static auto& get(ThisPtr this_ptr) { 
        return this_ptr->my_int;
    }

public:
    int& get() {
        return get(this);
    }

    const int& get() const {
        return get(this);
    }
};

This way you are free from the fear associated with using const_cast, mutable and other stuff that goes into trying to reduce code duplication in cases like this. If you get something wrong, the compiler will let you know.

Curious
  • 19,352
  • 6
  • 45
  • 114
  • 5
    Very neat use of template argument deduction. I still wish C++ had something to avoid the code duplication with these overloads. – StoryTeller - Unslander Monica Jun 26 '17 at 08:32
  • 1
    @StoryTeller maybe something like a member function with a trailing `` that deduces the `this` ptr from the context of usage? – Curious Jun 26 '17 at 08:38
  • 3
    I'd use that. If only the committee could be swayed to accept it.. – StoryTeller - Unslander Monica Jun 26 '17 at 08:39
  • 2
    "*Casting a const object to its non const version via const_cast is undefined behavior.*" That's not true. Attempting to modify this non-const-but-actually-const object is UB. – Hatted Rooster Jun 26 '17 at 08:51
  • 1
    This answer will give beginners like OP the wrong idea. The approach of πάντα ῥεῖ is more likely to help. – aggsol Jun 26 '17 at 08:55
  • @GillBates fixed – Curious Jun 26 '17 at 09:02
  • 1
    @downvoters why so many downvotes? the duplication in `const` and `non-const` methods was bothering OP, so this is a viable solution to the problem. – Curious Jun 26 '17 at 09:03
  • @aggsol it is incorrect to think that OP might have been a beginner. Maybe they just scaled down their issue to a smaller scale to help readers understand faster.. – Curious Jun 26 '17 at 09:03
  • @aggsol OP's comment should clarify that they had other things to do in the method as well. – Curious Jun 26 '17 at 09:21
  • How do we know the object is `const`? – Lightness Races in Orbit Jun 26 '17 at 09:22
  • @BoundaryImposition Sorry but I don't follow, which part of my answer are you talking about? – Curious Jun 26 '17 at 09:22
  • The part where you talk about "casting a const object". – Lightness Races in Orbit Jun 26 '17 at 09:26
  • @BoundaryImposition that was in reference to what OP was doing, and what further uses of such a cast might result in if not used carefully – Curious Jun 26 '17 at 09:27
  • It is not clear from the question that the OP is casting a const object, and comments in fact suggest otherwise. – Lightness Races in Orbit Jun 26 '17 at 09:28
  • @BoundaryImposition sure, but OP was in fact casting a pointer to const to a pointer to non-const right? – Curious Jun 26 '17 at 09:29
  • There is nothing inherently incorrect about doing that. – Lightness Races in Orbit Jun 26 '17 at 09:29
  • 1
    @BoundaryImposition but it is dangerous, before you know it, it will result in UB – Curious Jun 26 '17 at 09:30
  • Only if you are "casting a const object" (then modifying it), which the OP doesn't seem to be doing. – Lightness Races in Orbit Jun 26 '17 at 09:30
  • 1
    @BoundaryImposition OP was however casting a pointer to const to a pointer to non-const. Although the code as is is not UB, it might become UB in the future when used in bigger chunks of code. My answer alleviates the problem and fear of UB in doing that. So what is the issue here? – Curious Jun 26 '17 at 09:31
  • @BoundaryImposition also, I made sure to specify that you get UB only when you modify an incorrectly `const-cast`ed object later on – Curious Jun 26 '17 at 09:32
  • Then perhaps make clearer that your answer (correctly) addresses the general case, rather than this specific question. At the moment it _appears_ to the uninitiated to be making a false claim about the OP's situation. – Lightness Races in Orbit Jun 26 '17 at 09:40
  • @BoundaryImposition I would be happy to edit it to make it clearer, let me try – Curious Jun 26 '17 at 09:41
  • @BoundaryImposition better? – Curious Jun 26 '17 at 09:43
  • 1
    I didn't downvote myself, but it's worth noting that OP is following the getter de-duplication technique described by Scott Meyers in Effective C++ ([see here](https://stackoverflow.com/questions/123758/how-do-i-remove-code-duplication-between-similar-const-and-non-const-member-func)). Using `const_cast` to return a non-const reference is well defined **as long as the returned reference is not a member declared as `const`**. The main risk with OP's code is that using `const_cast` to lazy initialize a complex object may have side effects that break stuff; which would be a nightmare to debug. – Karl Nicoll Jun 26 '17 at 09:59
  • @KarlNicoll I feel like the template method above is a much better solution than the casting in that solution... One would have to know things like what you described and be extra careful, but with the template method, you don't need to worry about that sort of stuff – Curious Jun 26 '17 at 10:02
  • 1
    @downvoters I still don't get why this answer has downvotes. What have I gotten wrong here? – Curious Jun 26 '17 at 10:04
  • 1
    @KarlNicoll ok, I will remove it altogether, although it is true right? casting a const object to non const and then modifying it does result in UB. – Curious Jun 26 '17 at 10:11
  • @KarlNicoll done, hopefully it gives people incentive to upvote this answer and remove their downvotes now – Curious Jun 26 '17 at 10:12
  • 2
    Yep the modified version was better thanks. Shame you removed it ;) – Lightness Races in Orbit Jun 26 '17 at 11:30
  • 1
    @KarlNicoll: Why is that not necessarily true? Okay, modulo complexities with classes and member functions that don't really do anything and `mutable`... meh – Lightness Races in Orbit Jun 26 '17 at 11:31
  • @KarlNicoll it is _generally_ true, though. However, casting a `const` _reference_ to non-`const` and then modifying the referenced object might not yield undefined behaviour, _if_ the object referred to is itself not `const`. (i.e. it is possible to have a `const` reference to a non-`const` object). Were you perhaps conflating this? – davmac Jun 26 '17 at 12:03
  • @KarlNicoll: _"You're correct that calling const_cast on a const object could be undefined behaviour in the general case"_ No, it never is. _"In this case though it's not"_ You don't know that. The member could be `const`, and a modification could follow later. It's the _member_'s `const`ness that we're discussing, not the parent object's. Though from comments it doesn't appear to be `const` anyway (hence my earlier suggestion to Curious to rework the answer). – Lightness Races in Orbit Jun 26 '17 at 13:32
  • @BoundaryImposition - Just to be clear, I'm not advocating `const_cast<>()` in 99.999% of cases where it is used. Removing constness is almost never a good idea. In the specific case of avoiding code duplication in const + non-const method pairs, it can be useful. – Karl Nicoll Jun 26 '17 at 14:08
  • @KarlNicoll: _"it's obvious that my_int is mutable because it's a non-const member of a non-const object"_ We can't see either the member's declaration or the parent object's declaration. So, no, we know nothing of the sort! You are just guessing and claiming the guess to be fact. And yes I know what `static_cast` does; this point need not be repeated in every comment. – Lightness Races in Orbit Jun 26 '17 at 14:09
  • @BoundaryImposition - Fair point, I was looking at Curious' answer and assumed it was also declared the same in the question. We can agree for sure that if OP DID declare his int (or table object or whatever) as const, (s)he is in trouble. Sorry for any confusion :-) – Karl Nicoll Jun 26 '17 at 14:14
  • @BoundaryImposition & others - To avoid further confusion I've deleted 2 of my previous comments as they were based on a false assumption that OP's `Foo::member_variable` is non-const. – Karl Nicoll Jun 26 '17 at 14:22
  • This answer is not wrong, but come on..., suddenly templates need to be involved? This is the type of code that gives C++ bad reputation. – alfC Jun 26 '17 at 15:59
  • @alfC how does using templates in this situation give C++ a bad reputation? I don't think I understand. The alternatives are uglier wouldn't you say? – Curious Jun 26 '17 at 16:02
  • @Curious, I think the correct answer for the concrete case you give is that a getter is not needed. For the case where a getter is really needed, the problem can be solved even if C++ didn´t have templates. The point is that this type of code "solves" problem that shouldn´t exists in the first place, by introducing unnecessarely complex features of the language. – alfC Jun 26 '17 at 16:10
  • @alfC using elegant features in a language to *try* come up with elegant solutions to an irritating problem gives the language a certain elegance missing in other languages right :) However one must be careful to not overengineer, a fine line in the middle – Curious Jun 26 '17 at 16:12
9

Ignoring the issue of whether you really need a getter, the best solution when duplicating functionality in both a const and non-const method is to have the non-const method call the const method and cast away the const-ness of the result (i.e. the first of the two alternatives you present in the question).

The reason is simple: if you do it the other way around (with the logic in the non-const method), you could accidentally end up modifying a const object, and the compiler won't catch it at compile time (because the method is not declared const) - this will have undefined behaviour.

Of course this is only a problem if the "getter" is not actually a getter (i.e. if it is doing something more complicated than just returning a reference to a private field).

Also, if you are not constrained to C++11, the template-based solution presented by Curious in their answer is another way of avoiding this problem.

davmac
  • 18,558
  • 1
  • 32
  • 55
  • All are fine answers - so why are the 2 other answers that make the same point as this one dramatically downvoted in comparison...? – underscore_d Jun 26 '17 at 14:02
  • @underscore_d I don't know. To hazard a guess: I think some of the other answers have been more drastically edited, so perhaps they were initially less clear or even incorrect. (At the time I wrote this answer, it was the only one other than Curious' that clearly stated a solution in terms of the question actually asked). – davmac Jun 26 '17 at 14:08
  • @underscore_d Well, my answer stated the same as this one right from the start - and I interpreted the question as is now right from the start, however people seem to have interpreted the question rather in the sense of πάντα ῥεῖ at first thus not accepting mine (OP edited question in the meanwhile)... Pity that people never ever notice either question or answer having changed, taking downvotes back again (much less converting to upvotes). – Aconcagua Jun 26 '17 at 14:30
  • @Aconcagua Looking again, I see that indeed your answer was ok to begin with. I think the code examples in it threw me off, or I didn't see it initially, not sure. I have no idea why mine got upvoted where yours didn't. Have an upvote from me. :) – davmac Jun 26 '17 at 15:13
  • We often want to return a rvalue `int` from a `const` method, and a reference `int&` from a non-`const` method. In this case, you cannot cast `int` to `int&`. This could be a source of inconsistency. – anatolyg Jun 26 '17 at 16:22
2

Is there any advantage using one over the other: ...

No, both are bad because they violate the data encapsulation principle.

In your example you should rather make myInt a public member. There's no advantage to have getters for such case at all.

If you really want (need) getter and setter functions these should look like this:

class Foo
{
private: 
    mutable int myInt_;
 // ^^^^^^^ Allows lazy initialization from within the const getter,
 //         simply omit that if you dont need it.

public:
    void myInt(int value)
    {
        // Do other stuff ...
        myInt = value;
        // Do more stuff ...
    }

    const int& myInt() const
    {
        // Do other stuff ...
        return myInt_;
    }
}
πάντα ῥεῖ
  • 83,259
  • 13
  • 96
  • 175
  • Adding the usage of `mutable` as example would cover most bases. – aggsol Jun 26 '17 at 08:56
  • 1
    @aggsol What has `mutable` to do with it? Doing the way th OP had shown, has no advantage over having a public member variable, as I mentioned. – πάντα ῥεῖ Jun 26 '17 at 08:58
  • The problem for OP is the code where comment `// stuff here` is non-const. She/He wants a lazy evaluation getter. – aggsol Jun 26 '17 at 09:02
  • @aggsol That's still possible with my model (and `mutable` yes). Let me edit a bit. – πάντα ῥεῖ Jun 26 '17 at 09:04
  • Having a public member could break compatibility later when it's changed to a getter to enforce e.g. lazy initialization. Thus there is an advantage in having getters from the start. – WorldSEnder Jun 26 '17 at 13:16
  • 2
    @WorldSEnder That is the typical point claimed in favour of always coding getters, but how often does such a change ever occur in reality? and is it really frequent enough to justify all the extra boilerplate typing, if most of it will never be required? – underscore_d Jun 26 '17 at 14:01
  • 1
    @WorldSEnder I fully agree with _@underscore_d_. I have been using a lot of `struct`s, where getter / setter functions would be very unlikely to be ever needed. Especially such stuff like networking protocol headers and similar. – πάντα ῥεῖ Jun 26 '17 at 14:04
1

You don't say where myInt comes from, the best answer depends on that. There are 2+1 possible scenarios:

1) The most common case is that myInt comes from a pointer internal to the class.

Assuming that, this is the best solution which avoids both code duplication and casting.

class Foo{
    int* myIntP;
    ... 
    int& get_impl() const{
       ... lots of code
       return *myIntP; // even if Foo instance is const, *myInt is not
    }
public:
    int& get(){return get_impl();}
    const int& get() const{return get_impl();}
};

This case above applies to pointer array, and (most) smart pointers.

2) The other common case is that myInt is a reference or a value member, then the previous solution doesn't work. But it is also the case where a getter is not needed at all. Don't use a getter in that case.

class Foo{
     public:
     int myInt; // or int& myInt;
};

done! :)

3) There is a third scenario, pointed by @Aconcagua, that is the case of an internal fixed array. In that case it is a toss-up, it really depends what you are doing, if finding the index is really the problem, then that can be factored away. It is not clear however what is the application:

class Foo{
    int myInts[32];
    ... 
    int complicated_index() const{...long code...}
public:
    int& get(){return myInts[complicated_index()];}
    const int& get() const{return myInts[complicated_index()];}
};

My point is, understand the problem and don´t over engineer. const_cast or templates are not needed to solve this problem.


complete working code below:

class Foo{
    int* myIntP;
    int& get_impl() const{
       return *myIntP; // even if Foo instance is const, *myInt is not
    }
public:
    int& get(){return get_impl();}
    const int& get() const{return get_impl();}

    Foo() : myIntP(new int(0)){}
    ~Foo(){delete myIntP;}
};

#include<cassert>

int main(){
    Foo f1; 
    f1.get() = 5;
    assert( f1.get() == 5 );

    Foo const f2;
//    f2.get() = 5; // compile error
    assert( f2.get() == 0 );    
    return 0;
}
alfC
  • 10,293
  • 4
  • 42
  • 88
0

As you intend access to more complex internal structures (as clarified via your edit; such as providing an operator[](size_t index) for internal arrays as std::vector does), then you will have to make sure that you do not invoke undefined behaviour by modifying a potentially const object.

The risk of doing so is higher in the second approach:

int& get()
{
    // stuff here: if you modify the object, the compiler won't warn you!
    // but you will modify a const object, if the other getter is called on one!!!
    return myInt;
}

In the first variant, you are safe from (unless you do const_cast here, too, which now would really be bad...), which is the advantage of this approach:

const int& get() const
{
    // stuff here: you cannot modify by accident...
    // if you try, the compiler will complain about
    return myInt;
}

If you actually need to modify the object in the non-const getter, you cannot have a common implementation anyway...

Aconcagua
  • 19,952
  • 4
  • 31
  • 51
-2

Modifying a const object through a non-const access path [...] results in undefined behavior.

(Source: http://en.cppreference.com/w/cpp/language/const_cast)

This means that the first version can lead to undefined behavior if myInt is actually a const member of Foo:

class Foo
{
    int const myInt;
public:
    const int& get() const
    {
        return myInt;
    }
    int& get()
    {
        return const_cast<int&>(static_cast<const Foo*>(this)->get());
    }
};
int main()
{
    Foo f;
    f.get() = 10; // this compiles, but it is undefined behavior
}

The second version would not compile, because the non-const version of get would be ill-formed:

class Foo
{
    int const myInt;
public:
    int& get()
    {
        return myInt;
        // this will not compile, you cannot return a const member
        // from a non-const member function
    }
    const int& get() const
    {
        return const_cast<Foo*>(this)->get();
    }
};
int main()
{
    Foo f;
    f.get() = 10;  // get() is ill-formed, so this does not compile
}

This version is actually recommended by Scott Meyers in Effective C++ under Avoid Duplication in const and Non-const Member Function.

pschill
  • 4,030
  • 1
  • 10
  • 32
  • 2
    Technically correct - but from the nature of the question, providing a getter with non-const reference, I consider it more appropriate to assume myInt not being const - and problem with constness/UB in other questions rather being about `const Foo f; const_cast(&f)->get();`. – Aconcagua Jun 26 '17 at 09:13
  • @Aconcagua yes you are correct – Silouane Jun 26 '17 at 09:21
  • Note that, in case myInt is not const, the second version could potentially lead to UB in the future when the non-const getter modifies some state, e.g. because of lazy initialization. – WorldSEnder Jun 26 '17 at 13:19