Understanding Gsl::Narrow Implementation

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:

  1. Use reinterpret_cast and...
  2. 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?

  1. span("Hi") is {'H', 'i', '\0'} whereas string_span("Hi") is {'H', 'i'}. string_span checks for the terminating null character and does not include it in the span.
  2. 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. Neither span nor string_span have that constraint.


Related Topics



Leave a reply



Submit