I have read, both on this site and elsewhere, that the recommended programming style for C++ is to avoid using C-style casts, and prefer the C++-style static_cast
, dynamic_cast
, reinterpret_cast
, and const_cast
. The reasons for this recommendation generally boil down to (1) the C++ casts are safer because they are more specific and limited in meaning, and (2) they are intentionally verbose and ugly because you should not be using them often in good C++ code. However, this reasoning doesn't make as much sense when it comes to calling C functions from my C++ code.
In my C++ projects I am often forced to call OS-level libraries that only have a C interface, such as the POSIX sockets library for network operations. These C functions often expect the caller to make several pointer casts, and if I stick to only C++-style casts my code becomes clunky and verbose. For example, my networking code is littered with function calls like this:
std::size_t message_size;
ssize_t bytes_read = recv(fd, reinterpret_cast<char*>(&message_size), sizeof(message_size), MSG_WAITALL);
I'm not gaining any safety from using C++ casts (since reinterpret_cast
doesn't really add any more compile-time checks than a C-style cast), and the ugliness of the code isn't alerting me to a problem - there's no way to use socket functions without casting.
In light of the interface mismatch between C and C++, would it be reasonable to use C-style casts when calling C functions that require casting? Or are C-style casts always bad practice, and I should just accept the extra code bloat when I need to call C functions?
2 Answers 2
It's easy to prevent the verbosity of calling C functions from cluttering your code: wrap them. In the case of recv
, for example, you can simply do this:
template <typename T>
ssize_t recv(int fd, T& data) {
return ::recv(fd, reinterpret_cast<char*>(&data), sizeof(data), MSG_WAITALL);
}
You can improve on this by e.g. static_assert
ing that T
is trivially copyable or has some other desired properties.
Now if you use this function instead of the C recv
all over the code, you have reduced clutter and improved safety (you can't accidentally mismatch the data type and its size).
At the same time, you're still using C++-style casts, which prevents you from accidentally casting away constness.
The standard explains how the C style cast works (N4296):
5.4/4: The conversions performed by
- a
const_cast
,- a
static_cast
,- a
static_cast
followed by aconst_cast
,- a
reinterpret_cast
, or- a
reinterpret_cast
followed by aconst_cast
,can be performed using the cast notation of explicit type conversion. (...)
Without entering into the details, the rest of the paragraph also explains that the C cast relaxes some constraints that would apply to static_cast
in presence of pointers to derived classes. This relaxed attitude is one of the reasons why it is advised not to use it in C++ code.
In your case, using (char*)&message_size
would result in reinterpret_cast<char*>(&message_size)
to be generated by the compiler. So you wouldn't lose anything by using reinterpret_cast
directly.
Bjarne Stroustrup gives a couple of other reasons, why the C++ casting should be preferred in his book "The design and evolution of C++":
To sum up, old-style cast:
- Are a problem for understanding: they provide a single notation for several weakly related operations
- Are error-prone: almost every type combination [i.e. source type and target type] has a legal interpretation
- Are hard to spot in code and hard to find with simple tools
- Complicate the C and C++ grammars
The new cast operators represent a classification of the old's cast functionality.
Of course, in your special case, point 1, 2 and 4 are not a real issue. Nevertheless using the new style would bring you the benefit of point 3, and would get you used to the greater precision that is commonly used in C++ casting.
On the other side, IMHO, it would not be shocking to still use the C syntax if you restrict its use to the sole highlight in the code the interoperability with the C libraries.
-
I feel like saying point 1,2 and 4 do not matter in his case is a bit short-sighted. Looking at a correctly written snipped in isolation and state that the two ways are equivalent to the compiler is not an argument. If he had expected to be able to convert a primitive with static cast the compiler error would still have hinted him there was something is wrong in his reasoning. The compiler would still catch const - correctness issues. The reinterpret_cast still gives more information to the reader. Those points still stand. Apart from that, your answer still makes perfect sense though.Teimpz– Teimpz2018年01月03日 23:18:02 +00:00Commented Jan 3, 2018 at 23:18
static_casts
here andreinterpret_casts
there, you end up polluting...static_cast
orreinterpret_cast
to ever prevent problems, e.g., over a C-style cast.