cast a value

Hello. According to the next code, it seems that we have two alternatives in order to cast a value. But which one is the best practice?

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>

int main()
{
    const float lol = 7.77;
    
    std::cout << "Output : " << static_cast<int>(lol) << std::endl;
    std::cout << "Output : " << int(lol) << std::endl;
    
    return 0;
}
Last edited on
In C++ prefer the C++ style casts (static_cast, dynamic_cast, etc) over the C style cast int(lol). The C++ casts are, IMO, easier to spot and the compiler will catch errors when using the wrong cast and is type aware. The C casts are harder to spot and no type checking is done.


The static_cast.

The int() is a C-style cast. The only cast that C has. It is a blunt instrument.
The C++ has more than one *_cast and they are more focused than the C-style cast.

Imagine that you have million lines of code in thousand files and you can't remember where you do a cast. Which one is easier to find from code: static_cast or something?
Last edited on
when reading older code, note that (int)lol is also the C style cast.
One "problem" with the C-style cast is that, in some cases, it will actually convert the value from one type to another, similar to a static_cast, but sometimes it just reinterprets the bits as something else without performing any actual conversion at all, similar to a reinterpret_cast. The C++-style cats make that more explicit, which avoids unpleasant surprises and makes the code more readable.

(And there is no such thing as dynamic_cast in C at all)
Last edited on
The int() is a C-style cast. The only cast that C has. It is a blunt instrument.

int(lol)
is a functional cast expression, it's not part of C.
https://en.cppreference.com/w/cpp/language/explicit_cast
Ok. We should use the C++ style - more efficient.
dynamic_cast? What is the difference?
Last edited on
dynamic_cast is used to cast a pointer (or a reference) of a more general class to a more specific class.

It will return a NULL pointer (or throw an exception), if the cast to more specific type is not possible, because of the actual object's type.

https://cplusplus.com/doc/tutorial/typecasting/#dynamic_cast
Last edited on
There are 4 C++ casts:

static_cast<>
const_cast<>
reinterpret_cast<>
dynamic_cast<>

See:
https://en.cppreference.com/w/cpp/language/explicit_cast
https://en.cppreference.com/w/cpp/language/dynamic_cast

Cast operations provided by the standard library:
std::move, std::move_if_noexcept, std::forward, std::as_const
C++23: std::to_underlying, std::forward_like
reinterpret_cast<> allows to come back to the previous data type?
After reading the paper which you provide, it seems to me that there is no way to come back after a casting operation. Obviously we have to store the previous data before its casting. Or maybe not?
Last edited on
The C++ casts don't change the data upon which they are applied. They are applied to the returned data. If you use reinterpret_cast<> then the result is the type of the specified cast type applied to the given data. In the context of it's use, this may or may not make any sense... But from the returned data from a reinterpret_cast<>, there is no way to know what was the original data type.
reinterpret_cast<> allows to come back to the previous data type?


generally this is done on either very low level types like int, or in a known easy reshape.
take a look at what it is DOING.
this cast takes the RAW BYTES of an object and says "hey, that pile of bytes is a whazzit". Later, you can just cast the same bytes back to the original, and now its a widget instead of a whazzit, ok: the compiler can deal with that.

why would you do such a thing?
this is common in file and network 'serialization' where generic functions (like file.write) work off chunks of bytes, so you cast your object to a char pointer(bytes!) and get its size (sizeof) and say hey, go write the bytes from here to here to the disk or network. When you later read that file back, you cast the bytes you read back into your class.

for another example, just being funny the other day, I cast a double to an integer, did a bitwise operation to knock off the last wad of significant binary digits, and cast it back to double. Not something one normally would do, but these are the kinds of things you are doing with reinterpret casting: forcing bytes to mean what you want them to mean for a moment. Another example, if you were copying C style strings in a performance oriented program, and you padded all your string sizes to be a multiple of 8, then you can cast and copy them as integers 8 times faster than copying them one byte at a time. (its likely that your compiler library already does this, but its an example so go with me here).

R-cast is 'the weeds'. you should know what it does, and how to use it for writing a binary file or whatever, but don't get caught up here trying to figure out exotic ways to reshuffle bytes. Once in a very rare while you can do something really productive here; most of the time its either unnecessary hackery or exceedingly simple (like the write() example).
Last edited on
> I cast a double to an integer, did a bitwise operation to knock off the last wad of significant binary digits,
> and cast it back to double.
> Not something one normally would do, but these are the kinds of things you are doing with reinterpret casting:
> forcing bytes to mean what you want them to mean for a moment.

Strongly favour using std::bit_cast for stuff like this.

From the proposal:
Low-level code often seeks to interpret objects of one type as another: keep the same bits, but obtain an object of a different type.
Doing so correctly is error-prone: using reinterpret_cast or union runs afoul of type-aliasing rules yet these are the intuitive solutions developers mistakenly turn to.

Attuned developers use aligned_storage with memcpy, avoiding alignment pitfalls and allowing them to bit-cast non-default-constructible types.

This proposal uses appropriate concepts to prevent misuse. ... We should standardize this oft-used idiom, and avoid the pitfalls once and for all.
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0476r2.html


For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <limits>
#include <cstdint>
#include <bit>
#include <bitset>

int main()
{
    constexpr double d = -1234.5678 ;
    static_assert( sizeof(double) == sizeof(std::uint64_t) && std::numeric_limits<double>::is_iec559 ) ;
    constexpr std::uint64_t i = std::bit_cast<std::uint64_t>(d) ;
    std::cout << std::hex << std::showbase << i << ' ' << std::bitset<64>(i) << '\n' ;
    constexpr double d1 = std::bit_cast<double>( ( i << 1U ) >> 1U ) ;
    std::cout << std::fixed << std::showpos << d << ' ' << d1 << '\n' ;
}

https://coliru.stacked-crooked.com/a/1ba891395164cdda
Last edited on
seeplus posted the link, but it is worth taking a look through it:

https://en.cppreference.com/w/cpp/language/explicit_cast

The C-style cast simply runs through all the various C++ cast options and operates as the first one that works. Hence “it is a blunt instrument”.

The C++-style casts are targeted for a specific behavior, and fail if that behavior is not met. It is like using a scalpel.

Purists will tell you to always prefer the C++-style casts. They are probably right. For a lot of behavior, though, I find C-style casts just fine. I’m probably wrong.

Just be aware of how your code is expected to behave in its targeted environmental state. [edit] And always follow (project|company|employer)’s guidelines. [/edit]

I did not know about std::bit_cast. That’s pretty cool.
Last edited on
The C++ casts don't change the data upon which they are applied. They are applied to the returned data.

Right
1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

int main()
{
    const float lol = 7.77;
    
    std::cout << "Output : " << static_cast<int>(lol) << std::endl;
    std::cout << "Output : " << int(lol) << std::endl;
    std::cout << "Output : " << lol << std::endl;
    
    return 0;
}
That is actually a good point — and a point of confusion to the unwary newbie.

Both casts and references can create temporary objects. Modifying the temporary naturally leaves the original unchanged. And then leaves the poor newbie confused about why his reference object was not modified. Type punning is tricky business. Many SO questions appear because of this.
Topic archived. No new replies allowed.