c++castingparentheses

C-style vs Function-style: Best Cast for the Unused Variable/Function Warning


When using a cast to void in order to suppress/silence a warning for an unused variable or function there are different possible combinations:

static int foo(){ return 4; }

int main(){
    int x;

//   void   x;      // Error: void variable
//   void  (x);     // Error: void variable
    (void)  x;      // OK
    (void) (x);     // OK

//   void   foo();  // Error: new function declaration
//   void  (foo()); // Error: new function declaration
    (void)  foo();  // OK
    (void) (foo()); // OK

//   void   foo;    // Error: void variable
//   void  (foo);   // Error: void variable
    (void)  foo;    // OK
    (void) (foo);   // OK

    return 0;
}

In order to be able to use the second combination (01), that is, function-style cast, I tried enclosing the expression in parentheses and it seems to be correct:

(void  (x));
(void  (foo()));
(void  (foo));

This strategy cannot make the first combination (00) work as a cast either, and I interpret it is not needed neither in the third nor in the fourth. Of course, even more extra meaningless parentheses could be added. Actually, if my understanding is correct, the fourth combination (11) contains indeed such unnecessary parentheses, and it is just the same C-style cast as the third combination (10).

My question is which of these approaches is the best, while understanding that no extra needless parentheses are used. I did not explore in detail the syntax expansion of this kind of expressions, so maybe I am missing something.

Note that I will not use this construct in any macro, but directly on a variable/function. Besides, I am not concerned about side effects on volatile variables.

Links:


Solution

  • When using a cast

    Based on this, we can quickly eliminate three possibilities, as they are not casts. See syntax (1) and (2) on cppreference.com—a C-style cast requires parentheses around the type, while a function-style cast requires parentheses around the thing being cast. The first line in each of your groups fails these requirements.

    //   void   x;      // Not a cast; no parentheses
    //   void   foo();  // Not a cast; neither `void` nor `foo` in the parentheses
    //   void   foo;    // Not a cast; no parentheses
    

    an unused variable or function

    Based on this, we can eliminate your middle group of possibilities, as the point is to keep the function unused. When foo() appears in an expression (such as a cast), this syntax invokes foo causing it be used. (What you managed to do is invoke foo() to get 4, then cast that 4 to void.) When foo() appears outside an expression, you don't have a cast, so also not what is desired.

    //   void  (foo()); // Not OK: Would invoke `foo()` if this was parsed as a cast
    //  (void)  foo();  // Not OK: Invokes `foo()`
    //  (void) (foo()); // Not OK: Invokes `foo()`
    

    the fourth combination (11) contains indeed such unnecessary parentheses

    Correct, and I don't understand why you added parentheses when something simpler appeared to work. Maybe you did not realize that the thing being cast can be more complex than an identifier? When the thing being cast is complex, parentheses around it are often required due to operator precedence (a C-style cast is ranked 3 out of 17, so it has precedence over a lot of other operators).

        (void) (x);     // OK, but why add parentheses?
        (void) ((x));   // OK, but why add parentheses?
        (void) (((x))); // OK, but why add parentheses?
        (void) (foo);   // OK, but why add parentheses?
    
        (void) (9*((x + 2)/5)); // OK, but overly complex
    //  (void)  9*((x + 2)/5) ; // Not OK: casts `9` to `void` then multiplies
    

    My question is which of these approaches is the best

    The third line in each grouping is simple and traditional. Use it.

        (void)  x;      // OK
        (void)  foo;    // OK
    

    Strangely, you did not directly ask about the most interesting case, the second line in your groupings. It matches the syntax of a function-style cast, and there is the minimal number of parentheses (one pair of them). So why did these not work?

    The twist here is that the C++ standard allows declaring a variable in parenthesis. So these lines match the syntax of a variable declaration in addition to matching that of a function-style cast. By the "most vexing parse" rule, declarations win these contests.

    This also explains why adding parentheses helped in these cases. A variable declaration must begin with the type. Beginning with something not part of the type, such as your (, removes the possibility of parsing the line as a variable declaration.

    //   void  (x);     // Same as declaring: void x;
    //   void  (foo);   // Same as declaring: void foo;
    
    (void  (x));     // Not a valid declaration, so parsed as an expression
    (void  (foo())); // Not a valid declaration, so parsed as an expression
    (void  (foo));   // Not a valid declaration, so parsed as an expression
    

    extra meaningless parentheses

    Interesting that you linked to this, since some of your answers are there. However, I admit they are buried a bit and eyes might cross before getting to them. Focus on the code in the "Ambiguity resolution of vexing parses" section of the accepted answer. Among the examples are the following lines:

       S w(int(a));  // function declaration
       S y((int)a);  // object declaration
    

    The first line is a function declaration because in this code, int(a) declares a parameter in the same way that your void (x); declares a variable. The second line is an object declaration because (int)a cannot be a parameter declaration; it is a cast, similar to your (void) x;. The same rules are being demonstrated, just in a different context.


    Final note

    While there are some special rules for casting to void (e.g. user-defined conversions are not considered), the syntax is the same as for other types. So when your compiler complains about

    void   x;
    

    you might want to try changing void to another type. This trick tends to help people read what they wrote instead of what they meant to write. Let's change void to int and see what we get.

    int   x;
    

    Compare this to the line where you initially defined x...