Double Negation in C++

What double negation does in C

!!a will be either 0 or 1, and will be type int. It will be 0 for zero a, and 1 otherwise.

As for your choices (a) and (b), they are not equivalent, due to a possibility of a being negative. That aside, you could argue that a > 0 ? 1 : 0 is clearer but in performance-critical applications !!a may be better as it will not branch whereas a ternary conditional could dump the pipeline. But a good compiler will optimise out either way. I seldom use either since things like if (a) and if (!!a) are functionally equivalent.

double negation in C : is it guaranteed to return 0/1?

Yes, in C99, see §6.5.3.3/4:

The result of the logical negation operator ! is 0 if the value of its operand compares
unequal to 0, 1 if the value of its operand compares equal to 0. The result has type int.
The expression !E is equivalent to (0==E).

So !x and !!y can only yield 0 or 1, as ints.

For other operators, in C99, see also Is the "true" result of >, <, !, &&, || or == defined?

Double Negation in C++

It's a trick to convert to bool.

Why does double negation change the value of C++ concept?

Here the concept D is the same as the concept C

They are not. Constraints (and concept-ids) are normalized when checked for satisfaction and broken down to atomic constraints.

[temp.names]

8 A concept-id is a simple-template-id where the template-name is
a concept-name. A concept-id is a prvalue of type bool, and does not
name a template specialization. A concept-id evaluates to true if the
concept's normalized constraint-expression ([temp.constr.decl]) is
satisfied ([temp.constr.constr]) by the specified template arguments
and false otherwise.

And the || is regarded differently in C and D:

[temp.constr.normal]

2 The normal form of an expression E is a constraint that is defined
as follows:

  • The normal form of an expression ( E ) is the normal form of E.
  • The normal form of an expression E1 || E2 is the disjunction of the normal forms of E1 and E2.
  • The normal form of an expression E1 && E2 is the conjunction of the normal forms of E1 and E2.
  • The normal form of a concept-id C<A1, A2, ..., An> is the normal form of the constraint-expression of C, after substituting A1,
    A2, ..., An for C's respective template parameters in the
    parameter mappings in each atomic constraint. If any such substitution
    results in an invalid type or expression, the program is ill-formed;
    no diagnostic is required.
  • The normal form of any other expression E is the atomic constraint whose expression is E and whose parameter mapping is the identity
    mapping.

For C the atomic constraints are T::a and T::b.

For D there is only one atomic constraint that is !!(T::a || T::b).

Substitution failure in an atomic constraint makes it not satisfied and evaluate to false. C<A> is a disjunction of one constraint that is satisified, and one that is not, so it's true. D<A> is false since its one and only atomic constraint has a substitution failure.

double negation to check against not NULL

This practice originates from the C language. Before C had a boolean type.

When c is 0, !!c is also 0.

When c is any other value, !!c is always 1.

This converts c into a true 2-state boolean. Testing expressions like ( !!bool1 == !!bool2 ) or ( !!bool3 == TRUE ) will give the expected result, even if these values are different bitwise representations of "true".

Which is better, double negation or bitshift?

This solution

return (foo & 0x8) >> 3;

is the worst. If for example the magic constant 0x8 will be changed then you also need to change the magic constant 3. And moreover it can occur such a way that applying the operator >> will be impossible for example when you need to check more than one bit.

If you want to return either 1 (logical true) or 0 (logical false) I think that it will look more clear if to write

return (foo & 0x8) != 0;

or

return (foo & 0x8) == 0x8;

For example if instead of the magic constant 0x8 you will use a named constant (or variable) as for example MASK then this return statement

return ( foo & MASK ) == MASK; 

will not depend on the value of MASK.

Pay attention to that these two return statements

return (foo & MASK) != 0;

and

return ( foo & MASK ) == MASK; 

are not equivalent. The first return statement means that at least one bit is set in the variable foo while the second return statement means that exactly all bits corresponding to bits in MASK are set.

If the return type of the function is _Bool (or bool defined in <stdbool.h>) and you need to check whether at least one bit is set according to the bit mask then you can just write

return foo & MASK;

Confused by use of double logical not (!!) operator

It is not as simple as double negation. For example, if you have x == 5, and then apply two ! operators (!!x), it will become 1 - so, it is used for normalizing boolean values in {0, 1} range.

Note that you can use zero as boolean false, and non-zero for boolean true, but you might need to normalize your result into a 0 or 1, and that is when !! is useful.

It is the same as x != 0 ? 1 : 0.

Also, note that this will not be true if foo is not in {0, 1} set:

!!foo == foo

#include <iostream>

using namespace std;

int main()
{
int foo = 5;

if(foo == !!foo)
{
cout << "foo == !!foo" << endl;
}
else
{
cout << "foo != !!foo" << endl;
}



return 0;
}

Prints foo != !!foo.

What is the reason for the double negation -(-n)?

The first thing to note is that --n actually decreases n by 1 and evaluates to the new value, with the type char; so it does something very different to -(-n). Don't change the code to that!

-n performs a unary negation of n and is also an expresion of type int due to the type promotion rules of C. The further negation sets it back to the original value but with the type int retained.

So -(-n) is actually a verbose way of writing +n, which is often though to be a no-op but in this case it converts the type of n to an int.

I suspect the author is guarding themselves against errant refactoring and they were worried about mismatching the type of the argument with the format specifier %d.

But in this particular case it does not matter: sprintf will automatically promote the char type to an int, so it's perfectly safe to write

sprintf(str, "%d", n);

Do also consider reducing the size of the str buffer if that's "real" code, and consider using the safer snprintf variant.

(As a final remark note that a double negation can yield signed integral type overflow, so do use with caution.)



Related Topics



Leave a reply



Submit