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

Can I overload with unused arguments to make the code clear?

306 views
Skip to first unread message

DeMarcus

unread,
Mar 12, 2013, 7:47:01 PM3/12/13
to
Hi,

Consider this situation.

#include <iostream>
#include <functional>

class NullEntertainer
{
};

const NullEntertainer NULL_ENTERTAINER;

class Audience
{
public:
void watchShow( std::function<void()> show )
{
show();
}

void watchShow( std::nullptr_t ) = delete;

void watchShow( const NullEntertainer& )
{
std::cout << "No show today" << std::endl;
}
};

void theDancer()
{
std::cout << "Dance, dance, dance!" << std::endl;
}

int main()
{
Audience a;
a.watchShow( theDancer );
//a.runShow( nullptr ); // Won't compile.
a.watchShow( NULL_ENTERTAINER );
return 0;
}


What we want to achieve here is to be able to say that there is no
entertainer today. We could allow nullptr as an argument to runShow()
but when reading the code, NULL or nullptr ain't that clear all the
times, especially when a function takes several arguments. Yes, we
understand something is missing when we see nullptr, but /what/ is missing?

Another approach is that instead of watchShow( const NullEntertainer& )
we could have a watchShow() taking no arguments, but that would be
severely confusing in my opinion.

Is it justifiable to introduce a NullEntertainer, instantiate it as
NULL_ENTERTAINER and then overload watchShow() with a NullEntertainer
argument that isn't used, just to make the code slightly more clear and
explicit to read?

Or are there better ways to solve this?


Thanks,
Daniel


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

Johannes Sixt

unread,
Mar 13, 2013, 3:25:28 AM3/13/13
to
On 12 Mrz., 23:50, DeMarcus
<use_my_alias_here_at_hotmail_...@tellus.orb.dotsrc.org> wrote:
> int main()
> {
> Audience a;
> a.watchShow( theDancer );
> //a.runShow( nullptr ); // Won't compile.
> a.watchShow( NULL_ENTERTAINER );
> return 0;
>
> }
...
> Is it justifiable to introduce a NullEntertainer, instantiate it as
> NULL_ENTERTAINER and then overload watchShow() with a
> NullEntertainer argument that isn't used, just to make the code
> slightly more clear and explicit to read?

A much more obvious solution is to have a function with a new name:

a.watchBoringShow();

It doesn't make a difference for the call site because it must make
the decision between whether there is an entertainer or not anyway.
(That in itself sounds like a design error, but that may just be an
artefact of this simplified example.)

-- Hannes

Öö Tiib

unread,
Mar 13, 2013, 12:35:32 PM3/13/13
to
On Wednesday, 13 March 2013 00:50:06 UTC+2, DeMarcus wrote:
> Consider this situation.

The example is ground on dynamic polymorphism situation (nothing can be
more dynamic than entertainers) but you work with static polymorphism
tools (like overloads) here. May be you should retry?

> I understand what you tried here, been there done that.
> What we want to achieve here is to be able to say that there is no
> entertainer today. We could allow nullptr as an argument to runShow()
> but when reading the code, NULL or nullptr ain't that clear all the
> times, especially when a function takes several arguments.

If it is dynamic polymorphism or dynamic dispatch then get out of
immersion of nullptr. Make a polymorphic smart pointer that instead of
nullptr uses pointer to static "MissingEntertainer" object that
*implements* the interface of Entertainer (just does nothing). Such
pointer can then never be nullptr, can be always dereferenced etc.
I'd call it as robust_pointer. :-)

Dave Harris

unread,
Mar 14, 2013, 7:32:40 AM3/14/13
to
In article <513fa7e1$0$32111$1472...@news.sunsite.dk>,
use_my_alias_her...@tellus.orb.dotsrc.org (DeMarcus) wrote:
> Is it justifiable to introduce a NullEntertainer, instantiate it as
> NULL_ENTERTAINER and then overload watchShow() with a
> NullEntertainer argument that isn't used, just to make the code
> slightly more clear and explicit to read?

Yes. Although I wouldn't use all-capitals for a name which wasn't a
macro. I would probably use an enum rather than a class, which is
slightly lighter weight. For example, I have used:

enum Utf8_ { Utf8 };
enum Utf16_ { Utf16 };
enum Utf32_ { Utf32 };

StringUtf8 source( "sample" );
StringUtf16 destination( Utf8, source );

Here the first enum value isn't used, but the type is used to
statically resolve a conversion. It's redundant because the type of
source has the same information, but requiring it makes the conversion
more explicit. (In practice I'd give them different values in case
someone wanted to test or print them at run time.)

> Or are there better ways to solve this?

Sometimes renaming the function is clearer. Using the same function
name allows some (compile-time) polymorphism, but if you don't need it
then using a different function name makes the declarations cleaner
without adversely affecting the caller.

a.watchNullShow();

-- Dave Harris, Nottingham, UK.

DeMarcus

unread,
Mar 14, 2013, 7:35:05 AM3/14/13
to
On 2013-03-13 17:35, 嘱 Tiib wrote:
> On Wednesday, 13 March 2013 00:50:06 UTC+2, DeMarcus wrote:
>> Consider this situation.
>
> The example is ground on dynamic polymorphism situation (nothing can
> be more dynamic than entertainers) but you work with static
> polymorphism tools (like overloads) here. May be you should retry?
>
>> I understand what you tried here, been there done that. What we
>> want to achieve here is to be able to say that there is no
>> entertainer today. We could allow nullptr as an argument to
>> runShow() but when reading the code, NULL or nullptr ain't that
>> clear all the times, especially when a function takes several
>> arguments.
>
> If it is dynamic polymorphism or dynamic dispatch then get out of
> immersion of nullptr. Make a polymorphic smart pointer that instead
> of nullptr uses pointer to static "MissingEntertainer" object that
> *implements* the interface of Entertainer (just does nothing). Such
> pointer can then never be nullptr, can be always dereferenced etc.
> I'd call it as robust_pointer. :-)

I agree your solution is better, but you can still accidently provide
nullptr, right? So I'm still wondering, in order to prevent nullptr to
be used, is it good practice to do this?

void watchShow( std::nullptr_t ) = delete;

Thanks,
Daniel

DeMarcus

unread,
Mar 14, 2013, 7:37:28 AM3/14/13
to
On 2013-03-13 08:25, Johannes Sixt wrote:
> On 12 Mrz., 23:50, DeMarcus
> <use_my_alias_here_at_hotmail_...@tellus.orb.dotsrc.org> wrote:
>> int main()
>> {
>> Audience a;
>> a.watchShow( theDancer );
>> //a.runShow( nullptr ); // Won't compile.
>> a.watchShow( NULL_ENTERTAINER );
>> return 0;
>>
>> }
> ...

>> Is it justifiable to introduce a NullEntertainer, instantiate it as
>> NULL_ENTERTAINER and then overload watchShow() with a
>> NullEntertainer argument that isn't used, just to make the code
>> slightly more clear and explicit to read?
>
> A much more obvious solution is to have a function with a new name:
>
> a.watchBoringShow();
>
> It doesn't make a difference for the call site because it must make
> the decision between whether there is an entertainer or not anyway.
> (That in itself sounds like a design error, but that may just be an
> artefact of this simplified example.)

Yes, that's also a way to be more specific. It could have worked
unless my real code had this situation in the /constructor/ which I
can't change the name of without introducing even more special code,
e.g. the Named Constructor Idiom.

Thanks anyway,
Daniel

Öö Tiib

unread,
Mar 14, 2013, 10:17:26 AM3/14/13
to
On Thursday, 14 March 2013 13:35:05 UTC+2, DeMarcus wrote:
> On 2013-03-13 17:35, 嘱 Tiib wrote:
> > On Wednesday, 13 March 2013 00:50:06 UTC+2, DeMarcus wrote:
> > If it is dynamic polymorphism or dynamic dispatch then get out of
> > immersion of nullptr. Make a polymorphic smart pointer that instead
> > of nullptr uses pointer to static "MissingEntertainer" object that
> > *implements* the interface of Entertainer (just does nothing). Such
> > pointer can then never be nullptr, can be always dereferenced etc.
> > I'd call it as robust_pointer. :-)
>
> I agree your solution is better, but you can still accidently provide
> nullptr, right?

Because of new loose semantics of list-initializes I currently seem to
make all constructors besides default, copy and move 'explicit'.

With smart pointers I additionally tend to have factory functions
and prefer those when creating the pointers:

template<class T, class None, /*variadic template parameters*/>
robust_ptr<T,None> make_robust(/*variadic function parameters*/)
{/*usual stuff*/ return ret; }

typedef EntertainerPtr robust_ptr<Entertainer, MissingEntertainer>;

That makes it quite hard that nullptr sneaks in from somewhere
silently and if it comes explicitly from front doors then it will be
converted into MissingEntertainer*.

> So I'm still wondering, in order to prevent nullptr to
> be used, is it good practice to do this?
>
> void watchShow( std::nullptr_t ) = delete;

But the EntertainerPtr can't never be nullptr.
You will have only that:

void watchShow( Entertainer& );

Usage is like that:

Audience a;
EntertainerPtr p; //default constructs to MissingEntertainer*
a.watchShow( *p );

Works like charm.

DeMarcus

unread,
Mar 14, 2013, 9:40:29 PM3/14/13
to
> With smart pointers I additionally tend to have factory functions
> and prefer those when creating the pointers:
>
> template<class T, class None, /*variadic template parameters*/>
> robust_ptr<T,None> make_robust(/*variadic function parameters*/)
> {/*usual stuff*/ return ret; }
>
> typedef EntertainerPtr robust_ptr<Entertainer, MissingEntertainer>;
>
> That makes it quite hard that nullptr sneaks in from somewhere
> silently and if it comes explicitly from front doors then it will be
> converted into MissingEntertainer*.
>
>> So I'm still wondering, in order to prevent nullptr to
>> be used, is it good practice to do this?
>>
>> void watchShow( std::nullptr_t ) = delete;
>
> But the EntertainerPtr can't never be nullptr.
> You will have only that:
>
> void watchShow( Entertainer& );
>
> Usage is like that:
>
> Audience a;
> EntertainerPtr p; //default constructs to MissingEntertainer*
> a.watchShow( *p );
>
> Works like charm.
>
>

Your robust_ptr is a nice construction that I would like to elaborate
more with but I have a question. Doesn't your robust_ptr render
std::shared_ptr obsolete, or do you have some clean rule when to use
robust_ptr and when to use std::shared_ptr?


Thanks,
Daniel

Öö Tiib

unread,
Mar 15, 2013, 2:08:14 AM3/15/13
to
I use unique_ptr lot more than shared_ptr and robust_ptr is closer
to it. It is on lot of cases very good, but not magic bullet. I avoid it
when lack of object makes requests nonsense.

std::shared_ptr is expensive and so I use it only for cases with
true shared ownership. std::weak_ptr is usually just shortcut to someone
else's belongings and so if the true owner has released it for some
reason then the weak_ptr owner has nothing.

std::unique_ptr is quite nice pointer. The key question is what way it
is less likely confusing to read. I try to illustrate:

struct Washer {
// washers wash ;-)
void wash();
// sometimes with soap
void wash( Soap& );
};

struct SoapEater {
// will throw you with heavy things if you give no real soap
void eat( std::unique_ptr<Soap> );
};

Usage:

Washer w;
SoapEater e;
std::unique_ptr<Soap> soap = searchForSoap(); // unsure if found

if (!soap)
{
w.wash(); // washer can wash with water only
// leave eater alone
}
else
{
w.wash( *soap );
e.eat( std::move(soap) );

Seungbeom Kim

unread,
Mar 16, 2013, 7:47:49 AM3/16/13
to

On 2013-03-14 23:08, 嘱 Tiib wrote:
>
> struct Washer {
> // washers wash ;-)
> void wash();
> // sometimes with soap
> void wash( Soap& );
> };
>
> struct SoapEater {
> // will throw you with heavy things if you give no real soap
> void eat( std::unique_ptr<Soap> );
> };
>
> Usage:
>
> Washer w;
> SoapEater e;
> std::unique_ptr<Soap> soap = searchForSoap(); // unsure if found
>
> if (!soap)
> {
> w.wash(); // washer can wash with water only
> // leave eater alone
> }
> else
> {
> w.wash( *soap );
> e.eat( std::move(soap) );
> }

It was not clear in the OP's description whether the presence of
an entertainer was determined at run-time, but if the presence of
soap is determined at run-time as in your example above, I would
simply go for a pointer argument.

void Washer::wash(Soap* soap)
{
if (soap == NULL) {
// wash without soap
}
else {
// wash with *soap
}
}

--
Seungbeom Kim

Öö Tiib

unread,
Mar 16, 2013, 7:57:58 PM3/16/13
to
On Saturday, 16 March 2013 12:50:02 UTC+2, Seungbeom Kim wrote:
> On 2013-03-14 23:08, �� Tiib wrote:
> > struct Washer {
> > // washers wash ;-)
> > void wash();
> > // sometimes with soap
> > void wash( Soap& );
> > };
> >
> > struct SoapEater {
> > // will throw you with heavy things if you give no real soap
> > void eat( std::unique_ptr<Soap> );
> > };
> >
> > Usage:
> >
> > Washer w;
> > SoapEater e;
> > std::unique_ptr<Soap> soap = searchForSoap(); // unsure if found
> >
> > if (!soap)
> > {
> > w.wash(); // washer can wash with water only
> > // leave eater alone
> > }
> > else
> > {
> > w.wash( *soap );
> > e.eat( std::move(soap) );
> > }
>
> It was not clear in the OP's description whether the presence of
> an entertainer was determined at run-time, but if the presence of
> soap is determined at run-time as in your example above, I would
> simply go for a pointer argument.

I meet more often situations when presence of something is on some
cases known compile time and on some cases is not. Even if initially
it was always not known it tends to change when software evolves.

> void Washer::wash(Soap* soap)
> {
> if (soap == NULL) {
> // wash without soap
> }
> else {
> // wash with *soap
> }
> }

You have two functions in one so it can not be called "simpler". It may
be easily added as a third overload however:

void Washer::wash(Soap* soap)
{
if (soap == nullptr) {
wash();
}
else {
wash( *soap );
}
}

Works as well.


--

DeMarcus

unread,
Mar 19, 2013, 3:41:29 PM3/19/13
to
The Entertainer in my case is determined in compile-time. To go straight
to my problem, I have a class that takes a handle and a function with
how to deallocate the handle, like this.

class HandleHolder
{
public:
HandleHolder( int handle, std::function<void(int)> deallocator );
};

I /can/ use it like this.

HandleHolder myHandleHolder( someHandle, nullptr );

but I want to find a way to avoid using nullptr here since that forces a
documentation lookup what argument two actually is. If I instead write
something like:

HandleHolder myHandleHolder( someHandle, NO_DEALLOCATION );

then it's much more clear what's going on and I don't need to check the
documentation. If overloading the constructor so I can use:

HandleHolder myHandleHolder( someHandle );

is also not a good idea since it won't catch my attention that no
deallocation is used.

Tiib's robust_ptr could probably solve this, but let's say I want to
make HandleHolder generic like below, then I don't know how to use
robust_ptr in a simple way.

template<class T>
class HandleHolder
{
public:
HandleHolder( T handle, std::function<void(T)> deallocator );
};


/Daniel


--

red floyd

unread,
Mar 19, 2013, 8:11:47 PM3/19/13
to
On 3/19/2013 12:41 PM, DeMarcus wrote:
>]redacted]
> I have a class that takes a handle and a function with
> how to deallocate the handle, like this.
>
> class HandleHolder
> {
> public:
> HandleHolder( int handle, std::function<void(int)> deallocator );
> };
>
> I /can/ use it like this.
>
> HandleHolder myHandleHolder( someHandle, nullptr );
>
> but I want to find a way to avoid using nullptr here since that forces a
> documentation lookup what argument two actually is. If I instead write
> something like:
>
> HandleHolder myHandleHolder( someHandle, NO_DEALLOCATION );
>
> then it's much more clear what's going on and I don't need to check the
> documentation. If overloading the constructor so I can use:

namespace {
const std::function<void(int)> NO_DEALLOCATION = nullptr;
}

or, if you'd rather use the preprocessor:

#define NO_DEALLOCATION nullptr

Öö Tiib

unread,
Mar 20, 2013, 7:06:22 AM3/20/13
to

On Tuesday, 19 March 2013 21:41:29 UTC+2, DeMarcus wrote:
> To go straight to my problem, I have a class that takes a handle and
> a function with how to deallocate the handle, like this.
>
> class HandleHolder
> {
> public:
> HandleHolder( int handle, std::function<void(int)> deallocator );
> };
>
> I /can/ use it like this.
>
> HandleHolder myHandleHolder( someHandle, nullptr );

May be first write what you want to have:

/// Try_HandleHolder.cpp
#include "HandleHolder.hpp"
#include <iostream> // cout

void close( int i ) {
std::cout << "int handle " << i << " has been CLOSED!" << std::endl;
}

void erase( float f ) {
std::cout << "float handle " << f << " has been ERASED!" <<
std::endl;
}

int main() {

// Should be template and all that
typedef HandleHolder<float> FloatHandle;
FloatHandle h0( 0.0f, erase );

typedef HandleHolder<int> IntHandle;

// Telling that I do not want deallocator should work
IntHandle h1( 1, IntHandle::NoDeleter );
// Providing deallocator should work
IntHandle h2( 2, close );
// Providing nothing should work without deallocator
IntHandle h3( 3 );
// Providing nullptr should not compile; uncomment to check it
// IntHandle h4( 4, nullptr );
// Providing nullptr old way should not compile; uncomment to
check it
// IntHandle h5( 5, 0 );

}

Not sure if that is actually what you want but ... you get the idea.
Then try to write a class that supposedly does it:

/// HandleHolder.hpp
/// This code was in Usenet post of �� Tiib as mere illustration.
#include <iostream> // cout
#include <functional> // function

template<typename T>
struct HandleHolder {
typedef std::function<void( T )> Deleter;
static Deleter const NoDeleter;

explicit HandleHolder( T v, Deleter f = NoDeleter )
: value( v )
, deleter( f )
{}

explicit HandleHolder( T v, std::nullptr_t ) = delete;

~HandleHolder() {
if ( deleter != nullptr ) {
std::cout << "deallocator of "
<< value << " PRESENT!" << std::endl;
deleter( value );
}
else {
std::cout << "deallocator of "
<< value << " MISSING!" << std::endl;
}
}
private:
explicit HandleHolder( T v, std::nullptr_t );
T value;
Deleter deleter;
};

template <class T>
typename HandleHolder<T>::Deleter const HandleHolder<T>::NoDeleter =
nullptr;


Again not sure if it actually runs now at all but ... you get the
idea.

Then post your problem if you get stuck.

We are otherwise in difficulties to see that Entertainers are actually
deallocators (that possibly are needed in destructors) and that the
watchShows are actually constructors.

Seungbeom Kim

unread,
Mar 20, 2013, 7:10:44 AM3/20/13
to

On 2013-03-19 12:41, DeMarcus wrote:
>
> To go straight to my problem, I have a class that takes a handle and
> a function with how to deallocate the handle, like this.
>
> class HandleHolder
> {
> public:
> HandleHolder( int handle, std::function<void(int)> deallocator );
> };
>
> I /can/ use it like this.
>
> HandleHolder myHandleHolder( someHandle, nullptr );
>
> but I want to find a way to avoid using nullptr here since that forces a
> documentation lookup what argument two actually is.

Isn't that true for almost anything? :)
You have to either read the documentation or have read it and know it
in advance to understand which is which in, say, strcpy(a, b).

Likewise, users of HandleHolder are expected to understand its interface,
and I don't think having 'nullptr' or having nothing in the second
argument of the constructor is particularly confusing; just reading
the constructor declaration can provide enough clue what it means,
when the parameter is appropriately named as 'deallocator' or such.

> If I instead write something like:
>
> HandleHolder myHandleHolder( someHandle, NO_DEALLOCATION );
>
> then it's much more clear what's going on and I don't need to check the
> documentation.

This can be achieved by any of the following:

- void NO_DEALLOCATION(int) { }
- auto NO_DEALLOCATION = [](int){}; // Did I get the syntax right?

(But all-uppercase names are usually reserved for macros only.)

> If overloading the constructor so I can use:
>
> HandleHolder myHandleHolder( someHandle );
>
> is also not a good idea since it won't catch my attention that no
> deallocation is used.

It may depend on many things whether that is worth catching your
attention. If many elements here and there call for your attention,
it can be quite distracting.

You may also want to be reminded that the point of using the same class
in different situations often (though not always) lies in the wish to
concentrate on the similarities and hide the differences.

--
Seungbeom Kim

Bart van Ingen Schenau

unread,
Mar 20, 2013, 7:12:44 AM3/20/13
to

On Tue, 19 Mar 2013 12:41:29 -0700, DeMarcus wrote:

> The Entertainer in my case is determined in compile-time. To go straight
> to my problem, I have a class that takes a handle and a function with
> how to deallocate the handle, like this.
>
> class HandleHolder
> {
> public:
> HandleHolder( int handle, std::function<void(int)> deallocator );
> };
>
> I /can/ use it like this.
>
> HandleHolder myHandleHolder( someHandle, nullptr );
>
> but I want to find a way to avoid using nullptr here since that forces a
> documentation lookup what argument two actually is.

Why not just provide a function called NO_DEALLOCATION (or something else
appropriate) that can be passed as second argument and that performs a no-
op.

<snip>
> Tiib's robust_ptr could probably solve this, but let's say I want to
> make HandleHolder generic like below, then I don't know how to use
> robust_ptr in a simple way.
>
> template<class T>
> class HandleHolder
> {
> public:
> HandleHolder( T handle, std::function<void(T)> deallocator );
> };
>

How about this:

template<class T>
class HandleHolder
{
public:
static void no_deallocation(T) { /* do nothing */ }
HandleHolder( T handle, std::function<void(T)> deallocator );
};

usage:
HandleHolder<X> h(anX, HandleHolder<X>::no_deallocation);

>
> /Daniel

Bart v Ingen Schenau

goran...@googlemail.com

unread,
Mar 26, 2013, 6:57:05 AM3/26/13
to
If being explicit to what goes in, then how about:

const TYPE missingParamXYZ;
RETTYPE func(..., TYPE param, ...) {...}

and then, at the call site:

auto val = func(..., missingParamXYZ, ...);

or

auto val = func(..., paramXYZ, ...);

Goran.
0 new messages