Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Simplifying implementing non-const methods in terms of their const overloads

4 views
Skip to first unread message

Krzysztof Czaiński

unread,
May 10, 2009, 4:28:36 PM5/10/09
to
Hello,

I have read in a few places, and it seems reasonable, that it is good
to avoid code duplication, and implement non-const methods in terms of
their const overloads.

<code>
class A {
public:
const int& getX() const; ///< obtains x, perhaps logs access, and
returns x by const ref
int& getX() {
return const_cast<int&>( static_cast<const A*>( this )->getX
() );
}
// ...
};
</code>

To me, the line "return ..." doesn't look very clear, it is long and
complicated. In fact, often it is easier to duplicate the original getX
() code.

I think it would be nice to simplify this. So I came up with the
following:

<code>
template < typename T >
inline const T* toconst( T* t )
{
return t;
}
template < typename T >
inline T& unconst( const T& t )
{
return const_cast<T&>( t );
}
</code>

Which simplifies the primary example as follows:

<code>
class A {
public:
const int& getX() const; ///< obtains x, perhaps logs access, and
returns x by const ref
int& getX() {
return unconst( toconst( this )->getX() );
}
// ...
};
</code>

I am quite happy with this idea, but now I am wondering... Why haven't
I read of something like this? Does this solution have any drawbacks?
Are the names unconst and toconst fortunate? Is it possible to
simplify implementing non-const methods in terms their of const
overloads even more, without loss of clarity?

Cheers

--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Seungbeom Kim

unread,
May 11, 2009, 8:41:05 AM5/11/09
to

It just doesn't seem necessary; the amount of code and the number of
functions that you have to add and maintain and that others have to read
and get used to are not justified by the small notational convenience
you gain. I don't expect many programmers to find the original version
much harder to understand than yours.

In addition, your 'unconst' function hides the potentially unsafe cast
inside, and makes even those clients doing nasty things with it look
quite innocent.

If possible, a more useful and safer abstraction than the individual
'toconst' and 'unconst' functions could be the whole pattern of adding
const to *this, calling a const member function, and removing const
from the result. I'm not a C++0x expert, but I believe it can be done
in C++0x with rvalue references and variadic templates.

--
Seungbeom Kim

Martin T.

unread,
May 11, 2009, 11:43:37 AM5/11/09
to
Krzysztof Czaiński wrote:
> Hello,
>
> I have read in a few places, and it seems reasonable, that it is good
> to avoid code duplication, and implement non-const methods in terms of
> their const overloads.
>
> <code>
> class A {
> public:
> const int& getX() const; ///< obtains x, perhaps logs access, and
> returns x by const ref
> int& getX() {
> return const_cast<int&>( static_cast<const A*>( this )->getX
> () );
> }
> // ...
> };
> </code>
>

I seem to miss something completely here, because really what kind of
sense does it make to have a const-version of anything in the first
place, if you're then going to provide a non-const version of the same
thing??

The other way round certainly makes sense, and there you also do not
need to provide any casts, as they are implicitly done:
###
class A {
public:
int& getX(); ///< obtains x, ...
const int& getX() const {
return getX();
}
###

br,
Martin

Martin T.

unread,
May 11, 2009, 12:52:24 PM5/11/09
to
Martin T. wrote:
> Krzysztof Czaiński wrote:
>> Hello,
>>
>> I have read in a few places, and it seems reasonable, that it is good
>> to avoid code duplication, and implement non-const methods in terms of
>> their const overloads.
>> (...)
> (...)

> The other way round certainly makes sense, and there you also do not
> need to provide any casts, as they are implicitly done:
> ###
> class A {
> public:
> int& getX(); ///< obtains x, ...
> const int& getX() const {
> return getX();
> }
> ###
>
Hm. Seems I've been in a rather confused state when I wrote the above :)
You cannot call the non-const function from the const one. Silly me. My
code really doesn't make sense.

> Krzysztof Czaiński wrote:
> int& getX() {
> return unconst( toconst( this )->getX() );
> }

I cannot quite see how this is simpler than the version with the
const_cast, apart from having saved a few characters. I think it will
just confuse readers, whereas someone reading C++ should already know
what a const_cast does.

Stuart Golodetz

unread,
May 11, 2009, 2:22:55 PM5/11/09
to

That doesn't do what you think it does - for instance, the following
results in a stack overflow:

const A a;
a.getX();

The point being that you're calling the const version of getX() from
within getX(), since this is a pointer to a const A within const int&
getX() const.

Stu

Krzysiek Czaiński "Czajnik"

unread,
May 11, 2009, 6:01:57 PM5/11/09
to
On 11 Maj, 14:41, Seungbeom Kim <musip...@bawi.org> wrote:

> It just doesn't seem necessary; the amount of code and the number of
> functions that you have to add and maintain and that others have to read
> and get used to are not justified by the small notational convenience
> you gain. I don't expect many programmers to find the original version
> much harder to understand than yours.

I can see Your point. Still, to me my version seems clearer.
Especially if we change the name of struct A to SomeInterestingBase,
and int to MemberXType (which still is not bad, as the returned type
could be a template), the discussed line boils down to:
###
return const_cast<MemberXType&>(static_cast<const SomeInterestingBase*>
(this)->getX());
vs.
return unconst(toconst(this)->getX());
###

> In addition, your 'unconst' function hides the potentially unsafe cast
> inside, and makes even those clients doing nasty things with it look
> quite innocent.

I totally agree. If I stick with this whole idea, I am considering
changing the name unconst to cast_away_const to manifest the cast it
performs.

> If possible, a more useful and safer abstraction than the individual
> 'toconst' and 'unconst' functions could be the whole pattern of adding
> const to *this, calling a const member function, and removing const
> from the result. I'm not a C++0x expert, but I believe it can be done
> in C++0x with rvalue references and variadic templates.

> Seungbeom Kim

That would be perfect...
###
return call_const_overload( this, getX );
###
I begun by trying to implement just that, but I only came up with the
two functions toconst and unconst so far...

As I now think about it, we can accomplish somthing similar to the
above by adding a macro to the two mentioned template functions (and I
would rather avoid macros):
###
#define CALL_CONST_OVERLOAD( method ) unconst(toconst(this)->method())
...
return CALL_CONST_OVERLOAD( getX );
...
###

For now, I am going to stick with current C++, though it would be
interesting to find out, if and how this might be resolved in C++0x.

Thanks for this reply, and for the others too.
Krzysztof

--

Hakusa

unread,
May 11, 2009, 6:01:04 PM5/11/09
to
On May 11, 11:43 am, "Martin T." <0xCDCDC...@gmx.at> wrote:

> I seem to miss something completely here, because really what kind of
> sense does it make to have a const-version of anything in the first
> place, if you're then going to provide a non-const version of the same
> thing??

This is done a lot in the STL to return const_iterators and regular
iterators based on whether the container is const or not. It would be
silly if the fallowing did not fail to compile, right?

void f( const vector<int>& v )
{
typedef vector<int>::iterator It;
for( It it = v.begin(); it != v.end(); it++ )
*it += 5;
}

The compiler error you'll get here might make taking that const out of
there look real appetizing, but it's there for a reason. The vector
should not be modified. All one need do is change iterator to
const_iterator. Well, that and not try to assign it anything. ;p

> The other way round certainly makes sense, and there you also do not
> need to provide any casts, as they are implicitly done:
> ###
> class A {
> public:
> int& getX(); ///< obtains x, ...
> const int& getX() const {
> return getX();
> }
> ###

When you call getX, do you expect it to call getX the const version
(recursion), or getX the non-const version, resulting in a violation
of const correctness?

It will call itself again because "this" is const. Thus, cast "this"
to a non-const pointer to call the non-const version of the function.

But, if you look at the OP's code, he has the non-const version call
the const version. This is better because the whole point of "this"
being const is it shouldn't be changed, so it's a GOOD thing you can't
call a non-const function with it because that function could change
it! So, OP calls the const version with a non-const "this" and all is
happy.

Martin Eisenberg

unread,
May 12, 2009, 5:16:13 AM5/12/09
to
=?UTF-8?Q?Krzysiek_Czai=C5=84ski_=22Czajnik=22?= wrote:

> If I stick with this whole idea, I am considering changing the
> name unconst to cast_away_const to manifest the cast it performs.

In that case, make 'const_cast' a substring of your name for
searching.


Martin

--
Quidquid latine scriptum est, altum videtur.

Krzysiek Czaiński "Czajnik"

unread,
May 14, 2009, 4:35:17 AM5/14/09
to
So, my latest version is:

###


template < typename T >
inline const T* toconst( T* t )
{
return t;
}
template < typename T >

inline const T& toconst( T& t )


{
return t;
}
template < typename T >

inline T* unconst_cast( const T* t )
{
return const_cast<T*>( t );
}
template < typename T >
inline T& unconst_cast( const T& t )


{
return const_cast<T&>( t );
}

#define CALL_CONST_OVERLOAD( method_call ) \
unconst_cast( toconst(this)->method_call )

// example:
struct A
{
const int& getX() const;
int& getX() { return CALL_CONST_OVERLOAD( getX() ); }

const float* getF( int i ) const;
float* getF( int i ) { return CALL_CONST_OVERLOAD( getF(i) ); }
// ...
};
###

Looks good do me, thanks for all replies; I will be happy to read some
more thoughts about this stuff ;-)
Krzysztof

--

0 new messages