Understanding gsl::narrow implementation
This is checking for overflow. Lets look at
auto foo = narrow<int>(std::numeric_limits<unsigned int>::max())
T
will be int
and U
will be unsigned int
. So
T t = narrow_cast<T>(u);
will give store -1
in t
. When you cast that back in
if (static_cast<U>(t) != u)
the -1
will convert back to std::numeric_limits<unsigned int>::max()
so the check will pass. This isn't a valid cast though as std::numeric_limits<unsigned int>::max()
overflows an int
and is undefined behavior. So then we move on to
if (!details::is_same_signedness<T, U>::value && ((t < T{}) != (u < U{})))
and since the signs aren't the same we evaluate
(t < T{}) != (u < U{})
which is
(-1 < 0) != (really_big_number < 0)
== true != false
== true
So we throw an exception. If we go even farther and wrap back around using so that t
becomes a positive number then the second check will pass but the first one will fail since t
would be positive and that cast back to the source type is still the same positive value which isn't equal to its original value.
How to use gsl narrow cast
The GSL was initially created as Microsoft's implementation of the C++ Core Guidelines. But this is not the only implementation of it. One other implementation is the (header-only) gsl-lite.
gsl::narrow
is a static_cast
that checks if information is lost via the cast and will then throw an exception. Losing information means that casting back to the initial type will give a value different from the input.
In practice this means that gsl::narrow<unsigned>(42)
will just behave the same as static_cast
, while gsl::narrow<unsigned>(-42)
will throw. Another example would be gsl::narrow<int>(42.0)
(works like static_cast
) and gsl::narrow<int>(42.1)
(exception). As gsl::narrow
checks it's result, it could impact performance negatively e.g. when used inside hot loops.
In cases where you want to lose information (e.g. binning) but want to mark this behavior (make it searchable in your code), you can use gsl::narrow_cast
which is just a wrapper around static_cast
(same syntax, same behavior).
Both casts are introduced in the Core Guidelines here (ES.46).
Using gsl::narrow fails
You can't (and shouldn't try to) use anything other than a reinterpret_cast
to convert between a pointer and a non-pointer, or between pointers to different (unrelated) types. The gsl::narrow
function is just a 'fancy' version of static_cast
: Understanding gsl::narrow implementation.
Further, when writing programs that use the WinAPI or MFC, it is virtually impossible to completely avoid casting between pointer and non-pointer types; notably, many of the message handling routines take a pointer to some data or other as their lParam
argument (the LPARAM
type is defined as either __int64
or int
, depending on the target platform).
So, your suggestion is, IMHO, the best option:
- Use
reinterpret_cast
and...- Add appropriate pragma warning to suppress the warning.
However, you will most likely need to add that #pragma...
directive in many places in your code. So, what you can do is to create a 'helper' (or wrapper) cast of your own, which you can then use throughout your code.
For example, you can add the following to your "stdafx.h" (or "pch.h") file (or to any header that is included wherever the cast is needed):
template<typename T, typename U> static T inline pointer_cast(U src) noexcept
{
static_assert(sizeof(T) >= sizeof(U), "Invalid pointer cast"); // Check sizes!
__pragma(warning(suppress:26490)) // Note: no semicolon after this expression!
return reinterpret_cast<T>(src);
}
You can then use that pointer_cast
and avoid having to add the pragma
each time. Here's a typical example, using a potential message handler for the WM_NOTIFY
message in a custom dialog box class:
BOOL MyDialog::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT *pResult)
{
NMHDR* pHdr = pointer_cast<NMHDR*>(lParam);
switch (pHdr->code) {
//... remaining code ...
Note: on the use of the __pragma()
directive (rather than #pragma
), see here.
Is it possible to narrow only if needed?
You can leverage the fact that overload resolution favours a perfect argument type match for a non-template function over a function template:
#include <iostream>
// (A)
void bar(int y) { std::cout << "bar(int)\n"; }
// (B)
//void bar(long long) { std::cout << "bar(long long)\n"; }
// (C)
template <typename T>
void bar(T) = delete;
// (C.SP1)
template<>
void bar<long long>(long long y) { bar(narrow<int>(y)); }
int main() {
long long a = 12LL;
bar(a); // bar(int)
// bar(long long) if `bar(long long)` above is available.
}
If void bar(long long);
is not available, any call to bar(a)
for an argument a
of type long long
will favour the non-narrowing function template overload (C), whose primary template has been deleted to only allow invocation when T
is exactly long long
(no conversions) through the specialization (C.SP1). Once void bar(long long);
at (B) becomes available, it will be chosen as a better viable candidate by overload resolution than that of the function template candidate.
If you are worried that introducing an additional bar
overload (the function template above) may break overload resolution of bar
when compiling the library itself, you could add a public wrapper for bar
and place the overload resolution delegation above in the TU where the wrapper is defined, applying internal linkage for the added bar
overload by declaring it in an unnamed namespace. E.g.:
// foo.h
#pragma once
void foo(long long y);
// foo.cpp
#include "foo.h"
#include "gsl/gsl_narrow"
#include "the_lib/bar.h"
namespace {
template <typename T>
void bar(T) = delete;
template<>
void bar<long long>(long long y) { bar(narrow<int>(y)); }
} // namespace
void foo(long long y) {
bar(y);
}
What purpose does `gsl::string_span` aim at?
span("Hi")
is{'H', 'i', '\0'}
whereasstring_span("Hi")
is{'H', 'i'}
.string_span
checks for the terminating null character and does not include it in the span.string
is owning and spans are not, so comparing them is comparing apples and oranges.zstring_span
is a span with the constraint that the last character is a null character. Neitherspan
norstring_span
have that constraint.
Related Topics
Appending Std::Vector to Itself, Undefined Behavior
Reversing Order of Words in a Sentence
Confused by Squaring MACro Sqr in C
How to Keep My Topmost Window on Top
Remove Duplicates from a List<Int>
Error: 'I' Does Not Name a Type with Auto
Auto Keyword Behavior with References
Is There a Proper 'Ownership-In-A-Package' for 'Handles' Available
Is Uninitialized Data Behavior Well Specified
Wait for Input for a Certain Time
Std::Filesystem' Has Not Been Declared After Including <Experimental/Filesystem>
How Is This a Most Vexing Parse
Using Sendmessage to Send Wm_Close to Another Process
C++: Timing in Linux (Using Clock()) Is Out of Sync (Due to Openmp)
How to Make a Recursive Rule in Boost Spirit X3 in VS2017