"Simplifying" forward to const - Request for critique

G

Gennaro Prota

Hi,

I've been having this facility for a long while in my personal
toolset. I thought of it and then never used it.

template< typename C, typename Ret >
Ret &
forward_to_const(
Ret const & ( C::* p )() const,
C const & inst )
{
return const_cast< Ret & >( ( inst.*p )() ) ;
}

It would be used more or less like this:

class C
{
int m ;

public:

...

int const &
f() const
{
return m ;
}

int &
f()
{
#ifdef IMPL_MANUALLY
C const & me( *this ) ;
return const_cast< int & >( me.f() ) ;
#else
return forward_to_const( &C::f, *this ) ;
#endif
}
} ;

I know, of course, that it doesn't handle functions with
parameters, functions returning pointers or even non-member
functions. It isn't meant to be so much general. My question is
rather whether you think it's worth anything or just adds
clutter to the code. The initial motivation was that I didn't
want to think about the add-const/remove-const manouvre each
time I needed such two "twin" member functions, especially since
it doesn't happen often, so the thinking process is a tad
longer. But, having just encountered a case where I need the
twin overloads, I'm not seeing a clear advantage of using this
forward_to_const stuff.
 
Ö

Öö Tiib

Hi,

I've been having this facility for a long while in my personal
toolset. I thought of it and then never used it.

  template< typename C, typename Ret >
  Ret &
  forward_to_const(
      Ret const & ( C::*  p )() const,
      C const &           inst )
  {
      return const_cast< Ret & >( ( inst.*p )() ) ;
  }

It would be used more or less like this:

  class C
  {
      int                 m ;

  public:

      ...

      int const &
      f() const
      {
          return m ;
      }

      int &
      f()
      {
      #ifdef IMPL_MANUALLY
          C const &           me( *this ) ;
          return const_cast< int & >( me.f() ) ;
      #else
          return forward_to_const( &C::f, *this ) ;
      #endif
      }
  } ;

I know, of course, that it doesn't handle functions with
parameters, functions returning pointers or even non-member
functions. It isn't meant to be so much general. My question is
rather whether you think it's worth anything or just adds
clutter to the code. The initial motivation was that I didn't
want to think about the add-const/remove-const manouvre each
time I needed such two "twin" member functions, especially since
it doesn't happen often, so the thinking process is a tad
longer. But, having just encountered a case where I need the
twin overloads, I'm not seeing a clear advantage of using this
forward_to_const stuff.

Seems like a clutter. Forward to non-const from const, it converts
return value automatically:

class C
{
int m;

public:
// ...

int& f()
{
return m;
}

int const& f() const
{
return const_cast< C* >(this)->f();
}
};

Or if return values are identical then remove non-const member
entirely.
I miss the reason why to forward to const member in such a situation.
 
A

Alf P. Steinbach /Usenet

* Gennaro Prota, on 28.08.2010 19:46:
Hi,

I've been having this facility for a long while in my personal
toolset. I thought of it and then never used it.

template< typename C, typename Ret>
Ret&
forward_to_const(
Ret const& ( C::* p )() const,
C const& inst )
{
return const_cast< Ret& >( ( inst.*p )() ) ;
}

It would be used more or less like this:

class C
{
int m ;

public:

...

int const&
f() const
{
return m ;
}

int&
f()
{
#ifdef IMPL_MANUALLY
C const& me( *this ) ;
return const_cast< int& >( me.f() ) ;
#else
return forward_to_const(&C::f, *this ) ;
#endif
}
} ;

I know, of course, that it doesn't handle functions with
parameters, functions returning pointers or even non-member
functions. It isn't meant to be so much general. My question is
rather whether you think it's worth anything or just adds
clutter to the code. The initial motivation was that I didn't
want to think about the add-const/remove-const manouvre each
time I needed such two "twin" member functions, especially since
it doesn't happen often, so the thinking process is a tad
longer. But, having just encountered a case where I need the
twin overloads, I'm not seeing a clear advantage of using this
forward_to_const stuff.

I agree, for small functions it's easier to just duplicate the code. It's not
even a maintainance problem. However, for more intricate extensive func, e.g.


<code>

// Machinery that I suspect is in C++0x standard library:

template< class Type, bool isConst >
struct Constified
{ typedef Type T; };

template< class Type >
struct Constified< Type, true >
{ typedef Type const T; };

template< class Type >
struct IsConst
{ enum { yes = false }; };

template< class Type >
struct IsConst< Type const >
{ enum { yes = true }; };

// A macro saves the day, hurray!:

#define COCONST( Type ) \
typename Constified< Type, IsConst< ThisClass >::yes >::T

class C
{
private:
int m ;

template< class ThisClass >
static COCONST( int )& f( ThisClass* self )
{
return self->m;
}

public:
C(): m( 42 ) {}

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

int main()
{
C mut;
C const nonMut;

mut.f() = 41;
nonMut.f();
}
</code>


Do you think I should blog about this?

I'm just working on my Norwegian C++ intro (ch 5 now, delving into C++ types)
and haven't blogged about any pure C++ topic for a long time...


Cheers & hth.,

- Alf
 
G

Gennaro Prota

* Gennaro Prota, on 28.08.2010 19:46:

I agree, for small functions it's easier to just duplicate the code. It's not
even a maintainance problem. However, for more intricate extensive func, e.g.


<code>

// Machinery that I suspect is in C++0x standard library:

template< class Type, bool isConst >
struct Constified
{ typedef Type T; };

template< class Type >
struct Constified< Type, true >
{ typedef Type const T; };

template< class Type >
struct IsConst
{ enum { yes = false }; };

template< class Type >
struct IsConst< Type const >
{ enum { yes = true }; };

// A macro saves the day, hurray!:

#define COCONST( Type ) \
typename Constified< Type, IsConst< ThisClass >::yes >::T

class C
{
private:
int m ;

template< class ThisClass >
static COCONST( int )& f( ThisClass* self )
{
return self->m;
}

public:
C(): m( 42 ) {}

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

int main()
{
C mut;
C const nonMut;

mut.f() = 41;
nonMut.f();
}
</code>

Interesting. It's also more robust. It would be used in rare
cases, of course.
Do you think I should blog about this?

Well, dunno, but it was interesting.
I'm just working on my Norwegian C++ intro (ch 5 now, delving into C++ types)
and haven't blogged about any pure C++ topic for a long time...

I know. I was thinking to learn some Norwegian :)

(But if that is a "long time" let's just not talk about my
blog...)

<digression (I guess)>

The COCONST stuff reminds me of another toy that I've been
having in my toolset which hasn't had much usage either:
cv_qualifier::pass_through (the term "toy" isn't casual: a lot
of metaprogramming is more a game to gather fun from than
anything useful for real work. And, with time, I've found that
the fun diminishes. So, I keep a few things here and there that
looked "promising", "interesting" and/or had a vague aura of
"could be useful in the future" but then... hardly find that
that moments come. FWIW, I remember an old message on the
libstdc++ list where someone posted something complicated to
achieve the same thing, so I posted cv_qualifier::pass_through.
But I received a private message from one of the participants
that I was "embarassing them", as the code was not under GPL.
Heck. That might have been the only time it got a usage :))

Anyway, here's the marvel:

namespace meta {

template< typename T >
class cv_qualifier
{
public:
template< typename U>
struct pass_through
{ typedef U type; };
};

template< typename T >
class cv_qualifier< const T >
{
public:
template< typename U >
struct pass_through
{ typedef const U type; };
};

template< typename T >
class cv_qualifier< volatile T >
{
public:
template< typename U >
struct pass_through
{ typedef volatile U type; };
};

template< typename T >
class cv_qualifier< const volatile T >
{
public:
template< typename U >
struct pass_through
{ typedef const volatile U type; };
};

}

(The formatting testifies how old it is.)

The idea is that cv_qualifier< T >::pass_through< U >::type has
the "union" of the cv-qualifiers of T and U. So the static f in
your example could be written, if we wanted to make a real mess
out of it, as:

template< class ThisClass >
static
typename meta::cv_qualifier< ThisClass >::
template pass_through<
int >::type &
f( ThisClass* self )
{
return self->m;
}

</digression>

Digression and obfuscation end here :)
 
S

Stuart Golodetz

Öö Tiib said:
Seems like a clutter. Forward to non-const from const, it converts
return value automatically:

That's not such a hot plan, because the actual object on which you
called the const method might be const. Forwarding from non-const to
const is fine, but not the other way round.
class C
{
int m;

public:
// ...

int& f()
{
return m;
}

int const& f() const
{
return const_cast< C* >(this)->f();
}
};

Or if return values are identical then remove non-const member
entirely.
I miss the reason why to forward to const member in such a situation.

The return values are identical but the return *types* aren't - if you
remove the non-const member then you can't get an int& to m any more.
You tend to forward to the const member when:

(a) You need to access something via two different types depending on
whether the object's const or not (e.g. int& and const int&).

and

(b) There's some non-trivial logic involved in returning the right value
and you don't want to duplicate it.

In the above, (b) isn't satisfied, so you wouldn't bother - just write
return m in each case. But there are some situations when you would bother.

Cheers,
Stu
 
S

Stuart Golodetz

Stuart Golodetz wrote:
The return values are identical but the return *types* aren't - if you
remove the non-const member then you can't get an int& to m any more.
You tend to forward to the const member when:

(a) You need to access something via two different types depending on
whether the object's const or not (e.g. int& and const int&).

and

(b) There's some non-trivial logic involved in returning the right value
and you don't want to duplicate it.

In the above, (b) isn't satisfied, so you wouldn't bother - just write
return m in each case. But there are some situations when you would bother.

Oops, make that:

....situations *in which* you would bother.

Stu
 
J

James Kanze

But requires a const_cast for the call. (Still, it's the
solution I prefer as well.)
That's not such a hot plan, because the actual object on which
you called the const method might be const. Forwarding from
non-const to const is fine, but not the other way round.

Both work equally well. Overloading on const is only really
relevant (supposing no overload abuse) in cases where the
non-const version of the function allows the client code to
modify the object (by returning a non-const reference or pointer
to some interals), but doesn't modify the object itself in any
way. In this case, there's no problem calling the non-const
version from the const.
The return values are identical but the return *types* aren't
- if you remove the non-const member then you can't get an
int& to m any more. You tend to forward to the const member
when:
(a) You need to access something via two different types
depending on whether the object's const or not (e.g. int& and
const int&).

(b) There's some non-trivial logic involved in returning the
right value and you don't want to duplicate it.

In the above, (b) isn't satisfied, so you wouldn't bother -
just write return m in each case. But there are some
situations when you would bother.

Yes, and in those cases, I'd generally forward to the non-const
function from the const.

Note that it's also reasonable (often, anyway) for the const
version of the function to return by value, and the non-const by
reference, e.g.:

class Toto
{
int myValue;
public:
int& value()
{
return myValue;
}
int value() const
{
return const_cast<Toto*>(this)->value();
}
};

Obviously, you can't use forward to const here; only forward to
non-const.
 
S

Stuart Golodetz

James Kanze wrote:
But requires a const_cast for the call. (Still, it's the
solution I prefer as well.)


Both work equally well. Overloading on const is only really
relevant (supposing no overload abuse) in cases where the
non-const version of the function allows the client code to
modify the object (by returning a non-const reference or pointer
to some interals), but doesn't modify the object itself in any
way. In this case, there's no problem calling the non-const
version from the const.

Hmm - so is using const_cast to remove constness from a physically const
object ok if you don't actually then use the result? I may have been
labouring under the delusion that the actual cast on the physically
const object leads to formally undefined behaviour - if that's not true,
then I think I take back what I said above.
Yes, and in those cases, I'd generally forward to the non-const
function from the const.

Note that it's also reasonable (often, anyway) for the const
version of the function to return by value, and the non-const by
reference, e.g.:

class Toto
{
int myValue;
public:
int& value()
{
return myValue;
}
int value() const
{
return const_cast<Toto*>(this)->value();
}
};

Obviously, you can't use forward to const here; only forward to
non-const.

That's also a good point - cheers :)

Stu
 
K

Kai-Uwe Bux

Stuart said:
James Kanze wrote:


Hmm - so is using const_cast to remove constness from a physically const
object ok if you don't actually then use the result? I may have been
labouring under the delusion that the actual cast on the physically
const object leads to formally undefined behaviour - if that's not true,
then I think I take back what I said above.
[...]

The relevant clause in the standard is

[7.1.5.1/4]
Except that any class member declared mutable (7.1.1) can be modified, any
attempt to modify a const object during its lifetime (3.8) results in
undefined behavior.

This does not imply that any access of a const object through a non-const
path is undefined behavior. You have to actually modify the object. For
comparison, here is how the standard phrases the analogous rule for volatile
objects:

[7.1.5.1/7]
If an attempt is made to refer to an object defined with a volatile-
qualified type through the use of an lvalue with a non-volatile-qualified
type, the program behaviour is undefined.

That is much stronger.


Best

Kai-Uwe Bux
 
S

Stuart Golodetz

Kai-Uwe Bux said:
Stuart said:
James Kanze wrote:

Hmm - so is using const_cast to remove constness from a physically const
object ok if you don't actually then use the result? I may have been
labouring under the delusion that the actual cast on the physically
const object leads to formally undefined behaviour - if that's not true,
then I think I take back what I said above.
[...]

The relevant clause in the standard is

[7.1.5.1/4]
Except that any class member declared mutable (7.1.1) can be modified, any
attempt to modify a const object during its lifetime (3.8) results in
undefined behavior.

This does not imply that any access of a const object through a non-const
path is undefined behavior. You have to actually modify the object. For
comparison, here is how the standard phrases the analogous rule for volatile
objects:

[7.1.5.1/7]
If an attempt is made to refer to an object defined with a volatile-
qualified type through the use of an lvalue with a non-volatile-qualified
type, the program behaviour is undefined.

That is much stronger.


Best

Kai-Uwe Bux

Ah ok, thanks, that clears up my confusion :) (And I take back what I
claimed originally.)

Regards,
Stu
 

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