an "optional const"

R

.rhavin grobert

hello;-)

i frequently need the following construction:

ReturnParam § Function() §
{
/...do something.../
someType var § = something;
/...do something.../
return something;
}

where § may be 'const' or nothing. So, if the function is called as a
member function of a constant object, it needs exactly the same code
as its non-const sister, but with some additional 'const' somewhere in
the fuction.

To reduce code-doubling, it would be very nice to have some "optional
const", eg. something that the compiler automatically removes if the
fn is called on an non-const object and translates to 'const' if the
object is constant.

As i cant think of any possibility that this would break existing
code, and furthermore think it should be easy to implement.

So i'd like to know if someone sees any reason to not propose an
"optional const", and - just by the way ;-) - where i could to it...
 
R

.rhavin grobert

A 'const' in the return type does not matter.  The difference, however,
is sometimes like this:

     ReturnType Function() const

vs

     ReturnType& Function()

Therefore, it's not "const" versus "non-const" in the return value type,
it's a "temporary object" versus "reference" or

     ReturnType const& Function() const

vs

     ReturnType & Function()

, i.e. the 'const' in the return type is not where you show it.








AFAIK, folks who are sure that the 'do something' parts in those two
functions do not differ at all, simply write a const version and in the
non-const version do the const_cast in and out:

     ReturnType const& Function() const
     {
         /* do something */
     }

     ReturnType& Function()
     {
         return const_cast<ReturnType&>(
             const_cast<ThisClass const&>(*this).Function()
             );
     }

true for trivial functions, i use this macro for this:

#ifndef INLINE_NC
#define INLINE_NC(ret,fnc,fnnc,cls)\
inline ret fnc {return const_cast<ret>(static_cast<const cls
&>(*this).fnnc);}
#endif

but in not-so trivial cases, i often have to double the code.


struct Something;

Something* GetSomething()
{
SomethingOther* pS = DoSomething();
if (TestSomething(pS)
{
/*.. do something ..*/
return SomethingOther->Something();
} else {
/*.. do something else ..*/
return NULL;
}
}

Something const* GetSomething() const
{
SomethingOther const* pS = DoSomething();
if (TestSomething(pS)
{
/*.. do something ..*/
return SomethingOther->Something();
} else {
/*.. do something else ..*/
return NULL;
}
}
 
V

Vidar Hasfjord

but in not-so trivial cases, i often have to double the code.

struct Something;

Something* GetSomething()
{
SomethingOther* pS = DoSomething(); [...]
}

Something const* GetSomething() const
{
SomethingOther const* pS = DoSomething();
[...]

While your algorithm stays the same for both the const and non-const
member function you operate on different types; hence you need a
template. I guess the problem is that DoSomething uses other member
function that are also overloaded on the constness of the object. The
solution is to make /this/ an explicit parameter of a static member
function template:

Something* GetSomething () {
return DoGetSomething
<Something, SomethingOther> (*this);
}

const Something* GetSomething () const {
return DoGetSomething
<const Something, const SomethingOther> (*this);
}

template <class S, class SO, class This>
static S* DoGetSomething (This& self) {
SO* p = self.DoSomething ();
if (self.TestSomething (p)) {
/*.. do something ..*/
return p->GetSomething();
} else {
/*.. do something else ..*/
return 0;
}
}

Regards,
Vidar Hasfjord
 
R

Rolf Magnus

Victor said:
A 'const' in the return type does not matter. The difference, however,
is sometimes like this:

ReturnType Function() const

vs

ReturnType& Function()

I'd say it's

const ReturnType& Function() const

vs

ReturnType& Function()

i.e. reference to const versus reference to non-const...
ReturnType const& Function() const
{
/* do something */
}

ReturnType& Function()
{
return const_cast<ReturnType&>(
const_cast<ThisClass const&>(*this).Function()
);
}

.... and here, you write that, too.
 
V

Vidar Hasfjord

AFAIK, folks who are sure that the 'do something' parts in those two
functions do not differ at all, simply write a const version and in the
non-const version do the const_cast in and out:

Effectively, yes, but regarding syntax they try to avoid 'const_cast'. [...]
One general code reuse scheme goes like this:

    private:
        ReturnType& fooImpl() const ...

    public:
        ReturnType& foo() { return fooImpl(); }
        ReturnType const& foo() const { return fooImpl(); }[/QUOTE]

While this is often an acceptable solution it is not general. If
fooImpl should need to call other member functions with overload
resolution based on the constness of the object, then this solution
wont work. See code below.

Also, if fooImpl needs to return a reference to a member, as often is
the case, you still need a const_cast inside the implementation; since
fooImpl is const while the return value is not. In that case this
solution is no better than the cast'n'forward idiom.

The fully general and safe solution is to forward to a template
function:

// Const and non-const getter overloads

#include <iostream>
using namespace std;

typedef int X;

class C {
X x_;

public:
C () {}

void foo () {cout << "non-const\n";}
void foo () const {cout << "const\n";}

X& get () {
foo ();
return x_;
}

#if 0 // duplicate algorithm
const X& get () const {
foo ();
return x_;
}
#else // dangerous cast'n'forward idiom
const X& get () const {
return const_cast <C*> (this)->get ();
}
#endif

// safe template solution

#if 1 // C++98
X& get2 () {return do_get2 <X> (*this);}
const X& get2 () const {return do_get2 <const X> (*this);}

template <class R, class This>
static R& do_get2 (This& self) {
self.foo ();
return self.x_;
}
#else // C++0x
X& get2 () {return do_get2 (*this);}
const X& get2 () const {return do_get2 (*this);}

template <class This>
static auto do_get2 (This& self) -> decltype (self.x_)& {
self.foo ();
return self.x_;
}
#endif
};


void test_getter_overload ()
{
cout << "Non-const object tests:\n";
C c;
c.get (); // Ok, prints "non-const".
c.get2 (); // Ok, prints "non-const".

cout << "Const object tests:\n";
const C cc;
cc.get (); // Ouch, prints "non-const".
cc.get2 (); // Ok, prints "const".
}

Regards,
Vidar Hasfjord
 
V

Vidar Hasfjord

* Vidar Hasfjord:
[...]
  // safe template solution
#if 1 // C++98
  X& get2 () {return do_get2 <X> (*this);}
  const X& get2 () const {return do_get2 <const X> (*this);}
  template <class R, class This>
  static R& do_get2 (This& self) {
    self.foo ();
    return self.x_;
  }
[...]
I'm too crossed-eyed to really read the code, but I think it's one of those yes,
not "the" but "also one". Credit to some Easter Bloc guy (Russian?). Uh,

I don't follow/know.
correction, I see some "const_cast" above, and no matter how the code works that
shouldn't be there if it's the old code, above is *not* the old Russian scheme.

You misinterpret the example; my code included the cast'n'forward
solution only for illustration.
Anyway, the reason why nobody do such things in practice, but only as academic
exercises, is that it's far too much code size overhead for no real gain.

That's generally true. But for the problem presented by the OP I think
the template solution is appropriate. His common code may very well
depend on overload resolution based on object constness. Only a
template can provide that; unless he duplicates the code.

Regards,
Vidar Hasfjord
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,535
Members
45,007
Latest member
obedient dusk

Latest Threads

Top