views:

995

answers:

9

Sometimes a local variable is used for the sole purpose of checking it in an assert(), like so -

int Result = Func();
assert( Result == 1 );

When compiling code in a Release build, assert()s are usually disabled, so this code may produce a warning about Result being set but never read.

A possible workaround is -

int Result = Func();
if ( Result == 1 )
{
    assert( 0 );
}

But it requires too much typing, isn't easy on the eyes and causes the condition to be always checked (yes, the compiler may optimize the check away, but still).

I'm looking for an alternative way to express this assert() in a way that wouldn't cause the warning, but still be simple to use and avoid changing the semantics of assert().

(disabling the warning using a #pragma in this region of code isn't an option, and lowering warning levels to make it go away isn't an option either...).

+2  A: 

This is a bad use of assert, IMHO. Assert is not meant as an error reporting tool, it's meant to assert preconditions. If Result is not used elsewhere, it's not a precondition.

anon
+1 agreed ..(comment needs 10 characters) :)
Arnold Spence
In the current place in the code where Func() is called, it is a "can't happen" incident that this function return anything which isn't 1. So an assert() is appropriate here. This isn't error reporting / error checking - it is sanity checking.
Hexagon
So presumably your application is interested in the side-effects, rather than the return code (as the release code will ignore it)? In that case, I would place an assertion on the side-effects.
anon
assert can be used for checking preconditions, postconditions, class invariants or intermediate results needed in the implementation.
Daniel Daranas
@Neil: Suppose Func() is actually printf(), and the return value is the number of characters output -- how could you test the side effects other than by checking the return value?
j_random_hacker
+1  A: 

You should move the assert inside the function before the return value(s). You know that the return value is not an unreferenced local variable.

Plus it makes more sense to be inside the function anyway, because it creates a self contained unit that has its OWN pre- and post-conditions.

Chances are that if the function is returning a value, you should be doing some kind of error checking in release mode on this return value anyway. So it shouldn't be an unreferenced variable to begin with.

Edit, But in this case the post condition should be X (see comments):

I strongly disagree with this point, one should be able to determine the post condition from the input parameters and if it's a member function, any object state. If a global variable modifies the output of the function, then the function should be restructured.

Brian R. Bondy
It could be that Func() is generic enough to be used in multiple places, but *in this case* the return value must be 1. In other cases, other return values may be allowed, so you need to have the assertion here. Unless you want to start passing parameters into Func() to indicate why you're calling it and then have it decide whether to assert, but that seems overly complicated.
Graeme Perrow
Yes, I agree with Graeme Perrow's comment. This is the situation here.
Hexagon
I strongly disagree with this point, one should be able to determine the post condition from the input parameters and if it's a member function, any object state. If a global variable modifies the output of the function, then the function should be restructured.
Brian R. Bondy
Re: "Unless you want to start passing parameters into Func() to indicate why you're calling it" - Any function's input and current object state should determine its output. If there is any question of needing to pass why you're calling it to determine the return value, there are major design problems in play.
Brian R. Bondy
Say you're calling an function that returns an integer and in almost all cases, any return value is valid. In this particular case however, the return value must be 1 for whatever reason, and the function you're calling is external or generic enough that it cannot determine that reason. Then the assertion needs to be in the calling function. (cont.)
Graeme Perrow
Having said that, Brian's right -- if a return code *must* be X, then it's a good idea to have some *non-debug* code in addition to the assertion that handles the case when something other than X is returned.
Graeme Perrow
Yes - what I'd expect to see would be:assert(Result);if(!Result){ //opps - clean up code goes here }
xan
The main problem with putting the assert inside Func() is that many times you do not have control over Func() - for example if Func() is a Win32 API. The danger to be careful of here is that in that situation you should probably be handing the error rather than asserting.
Michael Burr
@Michael Burr: Yup I agree, it is not applicable if you don't have control of the func. But when you do it should be in there.
Brian R. Bondy
+12  A: 

We use a macro to specifically indicate when something is unused:

#define _unused(x) ((void)x)

Then in your example, you'd have:

int Result = Func();
assert( Result == 1 );
_unused( Result ); // make production build happy

That way (a) the production build succeeds, and (b) it is obvious in the code that the variable is unused by design, not that it's just been forgotten about. This is especially helpful when parameters to a function are not used.

Graeme Perrow
Until Result is later used, and then it's just confusing :) But nice suggestion +1.
Brian R. Bondy
also, Func is still called (unless the optimizer knows it has no side-effects)
xtofl
This is a good idea. I am using it for parameters to functions that are not used, but using it for assert()s is certainly a nice option as well.Maybe a macro called, say, FOR_ASSERT() can be used instead to make the reasoning clear.Thanks!
Hexagon
Fine, but a little verbose in this case. It is possible to avoid the intermediate variable.
Jem
@xtofl: That's a point, but almost certainly you *want* Func() to be called -- otherwise you could have just written "assert(Func() == 1);" in the first place.
j_random_hacker
A: 
int Result = Func();
assert( Result == 1 );
Result;

This will make the compiler stop complaining about Result not being used.

But you should think about using a version of assert that does something useful at run-time, like log descriptive errors to a file that can be retrieved from the production environment.

John Dibling
Result; is another warning - which is unused value or has no effect.
Mykola Golubyev
@Mykola Golubyev: Not on VC9 @ Warning Level 4. Which compiler are you using?
John Dibling
+5  A: 
int Result = Func();
assert( Result == 1 );

This situation means that in release mode, you really want:

Func();

But Func is non-void, i.e. it returns a result, i.e. it is a query.

Presumably, besides returning a result, Func modifies something (otherwise, why bother calling it and not using its result?), i.e. it is a command.

By the command-query separation principle (1), Func shouldn't be a command and a query at the same time. In other words, queries shouldn't have side effects, and the "result" of commands should be represented by the available queries on the object's state.

Cloth c;
c.Wash(); // Wash is void
assert(c.IsClean());

Is better than

Cloth c;
bool is_clean = c.Wash(); // Wash returns a bool
assert(is_clean);

The former doesn't give you any warning of your kind, the latter does.

So, in short, my answer is: don't write code like this :)

Update (1): You asked for references about the Command-Query Separation Principle. Wikipedia is rather informative. I read about this design technique in Object Oriented Software Construction, 2nd Editon by Bertrand Meyer.

Update (2): j_random_hacker comments "OTOH, every "command" function f() that previously returned a value must now set some variable last_call_to_f_succeeded or similar". This is only true for functions that don't promise anything in their contract, i.e. functions that might "succeed" or not, or a similar concept. With Design by Contract, a relevant number of functions will have postconditions, so after "Empty()" the object will be "IsEmpty()", and after "Encode()" the message string will be "IsEncoded()", with no need to check. In the same way, and somewhat symetrically, you don't call a special function "IsXFeasible()" before each and every call to a procedure "X()"; because you usually know by design that you're fulfilling X's preconditions at the point of your call.

Daniel Daranas
This advice is too general. What about `list.Remove`? Here you may *need* to have the return value whether an element was removed. `list.WasSuccessfullyRemoved()` would be bogus.
Konrad Rudolph
Returning an error code from a function is perfectly fine, but you seem to damn the practice.
John Dibling
@Daniel: Could you explain the rationale for the "command-query separation principle"? A link would be fine. Thanks!
j_random_hacker
@j_random_hacker, see Update (1).
Daniel Daranas
@John: As with all design approaches, the Command-Query Separation Principle is just a technique. A useful technique, IMO, and one that I usually try to follow. I'm ok if in some cases I find out that it is better to go a different way, but it seems that the situation described in the question might be a good candidate for applying it.
Daniel Daranas
@Daniel: Thanks for the update. I can see the advantages in this. OTOH, every "command" function f() that previously returned a value must now set some variable last_call_to_f_succeeded or similar, possibly causing problems for re-entrant/multithreaded code (as noted in the article). But yes, CQS definitely seems like a valid, logic-simplifying approach worth applying where possible.
j_random_hacker
@j_random_hacker: As with all techniques, it is always subject to evaluation. In many cases, I design like that, but I admit that sometimes I use the other strategy, especially in cases where nothing can be asserted about the result, e.g. "FindFile()" or "UpdateConfigurationFile()". It's just another technique in your toolset, to be used judiciously. However, stay tuned for update (2) :)
Daniel Daranas
Some good points Daniel :)
j_random_hacker
+2  A: 

You could use:

Check( Func() == 1 );

And implement your Check( bool ) function as you want. It may either use assert, or throw a particular exception, write in a log file or to the console, have different implementations in debug and release, or a combination of all.

Jem
A: 

I'd use the following:

#ifdef _DEBUG
#define ASSERT(FUNC, CHECK) assert(FUNC == CHECK)
#else
#define ASSERT(FUNC, CHECK)
#endif

...

ASSERT(Func(), 1);

This way, for release build, the compiler don't even need to produce any code for assert.

Donotalo
In release `Func()` is not called
Andrew Stein
For the else case you need "#define ASSERT(FUNC, CHECK) FUNC
Andrew Stein
If Func() is required only to return a value for asserting, then in release, the call is not necessary. But yes, otherwise it is necessary and I missed that.
Donotalo
+4  A: 

You could create another macro that allows you to avoid using a temporary variable:

#ifndef NDEBUG
#define Verify(x) assert(x)
#else
#define Verify(x) ((void)(x))
#endif

// asserts that Func()==1 in debug mode, or calls Func() and ignores return
// value in release mode (any braindead compiler can optimize away the comparison
// whose result isn't used, and the cast to void suppresses the warning)
Verify(Func() == 1);
Adam Rosenfield
+1  A: 

I wouldn't be able to give a better answer than this, that addresses that problem, and many more:

Stupid C++ Tricks: Adventures in assert

hcpizzi