Risks / costs of useless casts?

106 views Asked by At

(Env: Linux / RTOS, Compiler: gcc v.7.3.1)

Is there a risk or cost to useless casts?

Here's an example:

Project.h:

typedef int32 PRJ_Status_t;
...
#define PRJ_SUCCESS((PRJ_Status_t)0)

Project.cpp:

#include "Project.h" 
bool ProjectApp::init()
{
   ...
   return(status == static_cast<int32>(PRJ_SUCCESS));
}
4

There are 4 answers

1
SomeWittyUsername On

All casts except for dynamic_cast are performed at compilation time, so there is no cost to them during runtime. The risks are inherent to the casting itself - it's forcing by definition, overriding the automatic safety mechanisms of the language. In case of "useless" cast, the only risk is impact on readability and maintenance of the code.

2
Christian Stieber On

No. Any halfway decent compiler will simply do nothing for a "useless" cast.

Observe:

#include <cstdint>

typedef int32_t PRJ_Status_t;

#define PRJ_SUCCESS ((PRJ_Status_t)0)

#ifdef DO_CAST
#define cast(x) static_cast<int32_t>(x)
#pragma message "with cast"
#else
#define cast(x) (x)
#pragma message "without cast"
#endif

bool init()
{
   int32_t status=55;
   return(status == cast(PRJ_SUCCESS));
}
stieber@gatekeeper:~ $ g++ -DDO_CAST -S Test.cpp -o - | wc -l
Test.cpp:9:17: note: ‘#pragma message: with cast’
    9 | #pragma message "with cast"
      |                 ^~~~~~~~~~~
45
stieber@gatekeeper:~ $ g++ -S Test.cpp -o - | wc -l
Test.cpp:12:17: note: ‘#pragma message: without cast’
   12 | #pragma message "without cast"
      |                 ^~~~~~~~~~~~~~
45

Both versions produce 45 lines of assembler code. Now, while this doesn't really prove anything, it's a strong hint it's the same code.

Also, while your mileage may vary -- the C++ standard does not mandate that has to be the same code -- any decent compiler will recognize that it's "pointless" to make that cast, and just ignore it.

An actually more interesting question arises if a cast is "useless" today, but is not "useless" on, let's say, another platform. If, for example, your PRJ_Status_t ends up being int64_t, your previously useless cast will just convert it silently. Without the cast, you would have gotten a warning if you assign it to an int32_t...

0
JaMiT On

There is no runtime cost for this static_cast. There is a cognitive cost, though, in terms of taking more effort to understand the code.

Is there a risk? Well, let's do a comparison of casting to not casting. Here's your code dressed up for copy-compile-run to contrast the behaviors with and without a cast. (I had to guess the type of status. I chose what the cast is to, namely int32_t, but PRJ_Status_t would work equally well in this demonstration.)

#include <cstdint>
#include <iostream>

typedef int32_t PRJ_Status_t;

#define PRJ_SUCCESS ((PRJ_Status_t)0)

// ----------------------------------

bool init_cast()
{
   int32_t status = 0;
   return(status == static_cast<int32_t>(PRJ_SUCCESS));
}

bool init_bare()
{
   int32_t status = 0;
   return(status == PRJ_SUCCESS);
}

int main() {
    std::cout << std::boolalpha << "The result with casting is " << init_cast() << ".\n";
    std::cout << std::boolalpha << "The result without cast is " << init_bare() << ".\n";
}

Running this produces

The result with casting is true.
The result without cast is true.

Looks good. Oh wait, the lead designer has made a change. Due to some expected growth of your organization, the status needs to be expanded to 64 bits with your project number stored in the upper 32 bits. Your project is number 1, so you modify Project.h accordingly. Only the code above commented separator has changed.

#include <cstdint>
#include <iostream>

typedef uint64_t PRJ_Status_t;

#define PRJ_SUCCESS ((PRJ_Status_t)((1ULL << 32)))

// ----------------------------------

bool init_cast()
{
   int32_t status = 0;
   return(status == static_cast<int32_t>(PRJ_SUCCESS));
}

bool init_bare()
{
   int32_t status = 0;
   return(status == PRJ_SUCCESS);
}

int main() {
    std::cout << std::boolalpha << "The result with casting is " << init_cast() << ".\n";
    std::cout << std::boolalpha << "The result without cast is " << init_bare() << ".\n";
}

No sweat. The output is still good, right?

The result with casting is true.
The result without cast is false.

Your mileage may vary. Maybe ignoring the project number is the right thing to do. It shouldn't matter that you accidentally invoked a function not associated with your project to get the status, right?

1
463035818_is_not_an_ai On

Cost at runtime? No. Casting a int32_t to a int32_t does nothing.


Apart from that...

A static_cast is a signpost for the reader of the code. It alerts them to be cautious about what is happening in that line. If someone reads

return(status == static_cast<int32>(PRJ_SUCCESS));

They will wonder what the actual type of PRJ_SUCCESS is. It will take them two lookups, one to find the macro that defined PRJ_SUCCESS and another one to find the declaration of the type alias. All that only to experience a WTF-moment. They could have used their time for more useful things. Suppose you are looking for a bug in the code, the more WTF-moments there are in the code, the more time it will take to find a bug.