Reference documentation
Contents
- Contract and assertion checks:
gsl_Expects( pred ),gsl_Ensures( pred ),gsl_Assert( pred ), and more - Pointer annotations:
owner<P>,not_null<P>, andnot_null_ic<P> - Numeric type conversions:
narrow<T>( u ),narrow_failfast<T>( u ), andnarrow_cast<T>( u ) - Safe contiguous ranges:
span<T, Extent> - Bounds-checked element access:
at( container, index ) - Integer type aliases:
index,dim,stride,diff - String type aliases:
zstring,czstring,wzstring,cwzstring,u8zstring,cu8zstring,u16zstring,cu16zstring,u32zstring,cu32zstring - Ad hoc resource management (C++11 and higher):
finally( action ),on_return( action ), andon_error( action ) - Feature checking macros
- Polyfills
- Configuration options and switches
- Configuration changes, deprecated and removed features
Contract and assertion checks
(Core Guidelines reference: GSL.assert: Assertions)
There are several macros for expressing preconditions, postconditions, and invariants:
| Assertion level | precondition check | postcondition check | assertion | |
|---|---|---|---|---|
| always checked | gsl_FailFast() |
use to mark unreachable code | ||
| always evaluated | gsl_Verify(c) |
evaluates to c; raises an assertion failure if !c unless contract checking is disabled |
||
| default | gsl_Expects(c) |
gsl_Ensures(c) |
gsl_Assert(c) |
checked unless contract checking is disabled |
| debug | gsl_ExpectsDebug(c) |
gsl_EnsuresDebug(c) |
gsl_AssertDebug(c) |
checked in Debug build only |
| audit | gsl_ExpectsAudit(c) |
gsl_EnsuresAudit(c) |
gsl_AssertAudit(c) |
disabled by default; use if c is expensive to evaluate |
Example:
template< class RandomIt >
auto median( RandomIt first, RandomIt last )
{
gsl_Expects( first != last );
// Verifying that a range of elements is sorted is an expensive operation that changes the
// computational complexity of the function `median()` from 𝒪(1) to 𝒪(n). Therefore, we
// express it as an audit-level contract check.
gsl_ExpectsAudit( std::is_sorted( first, last ) );
auto count = last - first;
return count % 2 != 0
? first[ count / 2 ]
: std::midpoint( first[ count / 2 ], first[ count / 2 + 1 ] );
}
Unlike gsl_Assert( c ), which may be elided depending on configuration switches, gsl_Verify( c ) guarantees that the
expression c is evaluated. This implies that gsl_Verify() can be used for expressions with side-effects, for instance when
checking return values:
gsl_Verify( CloseHandle( hModule ) );
Unlike gsl_Assert( c ), which is a statement, gsl_Verify( c ) is an expression with a Boolean value equivalent to c.
It may be used to handle assertion failures defensively if runtime contract enforcement is disabled:
if( ! gsl_Verify( CloseHandle( hModule ) ) )
{
std::println( stderr,
"BUG: closing module handle {} failed; we probably forgot to call DuplicateHandle() somewhere",
static_cast<void const *>( hModule ) );
}
The behavior of the different flavors of pre-/postcondition checks and assertions depends on a number of configuration macros:
-
The macros
gsl_Expects(),gsl_Ensures(), andgsl_Assert()are compiled to runtime checks unless contract checking is disabled with thegsl_CONFIG_CONTRACT_CHECKING_OFFconfiguration macro. -
The macro
gsl_Verify()is compiled to a runtime check.gsl_Verify( c )evaluates to the Boolean value ofc. Additionally, if contract checking is enabled,gsl_Verify( c )will enact a contract violation likegsl_Assert( c )ifcevaluates tofalse. -
Contract checks expressed with
gsl_ExpectsAudit(),gsl_EnsuresAudit(), andgsl_AssertAudit()are discarded by default. In order to have them checked at runtime, thegsl_CONFIG_CONTRACT_CHECKING_AUDITconfiguration macro must be defined. -
The macros
gsl_ExpectsDebug(),gsl_EnsuresDebug(), andgsl_AssertDebug()are compiled to runtime checks unless contract checking is disabled by defininggsl_CONFIG_CONTRACT_CHECKING_OFFor assertions are disabled by definingNDEBUG. They can be used in place of theassert()macro from the C standard library. (Note that defininggsl_CONFIG_CONTRACT_CHECKING_AUDITalso enables checking of thegsl_*Debug()macros regardless of whetherNDEBUGis defined.) -
The
gsl_Expects*(),gsl_Ensures*(),gsl_Assert*()categories of checks can be disabled individually with thegsl_CONFIG_CONTRACT_CHECKING_EXPECTS_OFF,gsl_CONFIG_CONTRACT_CHECKING_ENSURES_OFF, orgsl_CONFIG_CONTRACT_CHECKING_ASSERT_OFFconfiguration macros. -
gsl_FailFast()is similar togsl_Assert( false )but is guaranteed to interrupt the current path of execution even if contract checking is disabled with configuration macros. It is useful in places which should not be reachable during program execution. For example:enum class Color : int { red, green, blue }; std::string colorToString( Color color ) { switch (color) { case Color::red: return "red"; case Color::green: return "green"; case Color::blue: return "blue"; } gsl_FailFast(); }The C++ language permits casting any representable integer value to an enum. Therefore,
colorToString(Color(0xFF00FF))is legal C++, but not actually supported by thiscolorToString()implementation.gsl_FailFast()is employed here to ensure that passing unsupported values tocolorToString()will be detected at runtime.gsl_FailFast()behaves as if annotated by the[[noreturn]]attribute. A C++11 compiler will therefore not emit a warning about a missing return statement incolorToString().
When compiling for C++20 or later, the following contract checking macros are available in addition:
| Assertion level | assertion | |
|---|---|---|
| always checked | gsl_FailFastAt(loc) |
use to mark unreachable code |
| always evaluated | gsl_VerifyAt(loc,c) |
evaluates to c; raises an assertion failure if !c unless contract checking is disabled |
| default | gsl_AssertAt(loc,c) |
checked unless contract checking is disabled |
| debug | gsl_AssertAtDebug(loc,c) |
checked in Debug build only |
| audit | gsl_AssertAtAudit(loc,c) |
disabled by default; use if c is expensive to evaluate |
These macros take an additional argument of type std::source_location.
They behave like their conventional counterparts except that the source location represented by loc, rather than
__FILE__ and __LINE__, is passed to the assertion handler.
This is useful for defining functions with assertion semantics:
// my-header.hpp
template <std::integral T, std::integral U>
T narrowFailFast( U value, std::source_location loc = std::source_location::current() )
{
gsl_AssertAt( loc, std::in_range<T>( value ) );
return static_cast<T>( value );
}
// my-source.cpp
#include "my-header.hpp"
int main()
{
int i = -1;
auto u = narrowFailFast<unsigned>( i ); // my-source.cpp:5: Assertion `narrowFailFast: std::in_range<T>( value )' failed
}
Had gsl_Assert() been used instead of gsl_AssertAt(), the assertion message would have
referenced the location my-header.hpp:4 instead of the call site my-source.cpp:5.
Pointer annotations
(Core Guidelines reference: GSL.view: Views)
gsl-lite defines the three pointer type annotations owner<P>, not_null<P>, and not_null_ic<P> which let the user signify additional intent:
owner<P>indicates that a raw pointerPcarries ownership of the object it points to, and is thus responsible for managing its lifetime.not_null<P>andnot_null_ic<P>indicate that a pointerPmust not benullptr.
owner<P> (C++11 and higher)
gsl_lite::owner<P> is a type alias that solely serves the purpose of expressing intent:
template< class P >
requires ( std::is_pointer_v<P> )
using owner = P;
(Note: The actual definition uses a SFINAE constraint to support C++11 to C++17.)
As far as the type system and the runtime behavior is concerned, owner<P> is exactly equivalent to P. However, the annotation conveys
intent to a reader, and static analysis tools may use the annotation to impose semantic checks based on the assumption of ownership.
If possible, a smart pointer with automatic lifetime management such as std::unique_ptr<>
or std::shared_ptr<> should be used instead. The purpose of owner<> is annotation of
raw pointers which cannot currently be replaced with a smart pointer.
not_null<P>
gsl_lite::not_null<P> is a class template that wraps a pointer type P while enforcing non-nullability.
not_null<P> has no default constructor, and its unary constructors use gsl_Expects() to check
that their argument is not nullptr. Therefore, when attempting to construct a not_null<P> from a null pointer, a runtime contract violation is triggered:
using gsl_lite::not_null;
int i = 42;
int * pi = &i;
int * pn = nullptr;
// compile error: no default constructor
//not_null<int *> n;
// compile error: no implicit conversion
//not_null<int *> npi = pi;
// explicit conversion: runtime null check
not_null<int *> npi = not_null( pi );
// compile error: no implicit conversion
//not_null<int *> nn = nullptr;
// compile error: no explicit conversion
//not_null<int *> nn = not_null( nullptr );
// explicit conversion: runtime null check ⇒ runtime contract violation
not_null<int *> npn = not_null( pn );
Motivation
When defining function signatures, it is customary to use pointers and references to indicate whether an object reference is required or optional:
void lock( Mutex & m ); // requires a valid object reference
struct ListNode
{
ListNode* prev;
ListNode* next;
int payload;
};
bool tryRemove( ListNode * x ); // also accepts a `nullptr`
But this convention does not apply to every situation. For example, accepting a pointer argument can also emphasize the fact that the object’s memory address may be taken and stored, as in the case of a list or tree node. Storing the memory address of a by-reference argument “feels” wrong, and may be flagged by static analyzers:
void insertAfter( ListNode & x, ListNode & newNode )
{
newNode.prev = &x; // <-- Eww.
newNode.next = x.next;
x.next = &newNode; // <-- Eww.
}
The function would be less awkward if it accepted pointers.
To make sure a pointer argument is not nullptr, we can add a precondition check to the function:
void insertAfter( ListNode * x, ListNode * newNode )
{
gsl_Expects( x != nullptr );
gsl_Expects( newNode != nullptr );
newNode->prev = x;
newNode->next = x->next;
x->next = newNode;
}
Writing all the precondition checks against nullptr quickly becomes tedious. And unlike the contract checks once envisioned
for C++20, precondition checks expressed with gsl_Expects() are not part of the function signature, which therefore does
not convey that it cannot handle nullptr input.
This is where not_null<> comes in. With not_null<>, the precondition can be “lifted” into the type system,
and thus into the function signature:
void insertAfter( not_null<ListNode *> x, not_null<ListNode *> newNode )
{
newNode->prev = x;
newNode->next = x->next;
x->next = newNode;
}
All not_null<> constructors check their arguments for nullptr with gsl_Expects(), so the functions above can already assume
that their arguments will never be nullptr, and the explicit precondition checks can be omitted.
When calling the function insertAfter(), it is understood that the caller passes ownership of the node newNode.
Transfer of ownership is best expressed with a smart pointer such as std::unique_ptr<>, which can also be used as an
argument to not_null<>:
void insertAfter( not_null<ListNode *> x, not_null<std::unique_ptr<ListNode>> newNode );
A not_null<std::unique_ptr<>> can be passed around just like a std::unique_ptr<>:
void insertAfterEnd( not_null<ListNode *> lastNode, not_null<std::unique_ptr<ListNode>> newNode )
{
gsl_Expects( lastNode->next == nullptr );
insertAfter( lastNode, std::move( newNode ) );
}
Because newNode is non-nullable, releasing the pointer it holds is not straightforward:
void insertAfter( not_null<ListNode *> x, not_null<std::unique_ptr<ListNode>> newNode )
{
newNode->prev = x;
newNode->next = x->next;
x->next = newNode.release(); // error: `not_null<>` has no member function `release()`
}
To extract the raw pointer, we first have to extract a nullable unique_ptr<>:
std::unique_ptr<ListNode> rawNewNode = std::move( newNode );
x->next = rawNewNode.release();
This can be written as a one-liner with gsl_lite::as_nullable():
x->next = gsl_lite::as_nullable( std::move( newNode ) ).release();
Reference
gsl_lite::not_null<>
not_null<P> strives to behave like the underlying type P as transparently as reasonably possible:
- There is no runtime size overhead:
sizeof( not_null<P> ) == sizeof( P ). not_null<P>implicitly converts toQifPimplicitly converts toQ.
For example,not_null<int *>implicitly converts toint const *.not_null<P>explicitly converts toQifPexplicitly converts toQ.not_null<P>can be dereferenced ifPcan be dereferenced.- If
Pcan be copied,not_null<P>can be copied. IfPcan be moved,not_null<P>can be moved.
For example,not_null<T *>is copyable,not_null<std::unique_ptr<T>>is movable but not copyable, andnot_null<std::shared_ptr<T>>is copyable and movable. - If
Ppoints to astruct,class, orunion,not_null<P>defines the member access operatoroperator->. - If
Phas a member functionP::get(),not_null<P>also defines a member functionnot_null<P>::get()which forwards toP::get(). - If
Pis a function pointer or a nullable function object such asstd::function<>,not_null<>defines the function call operatoroperator()which forwards the call toP:long call( not_null<long (*)(long)> func, long arg ) { return func( arg ); } Qimplicitly converts tonot_null<P>ifQis non-nullable and implicitly converts toP.
For example,std::labsimplicitly converts tonot_null<long (*)(long)>:long callAbs( long val ) { return call( std::labs, val ); }Likewise, a string literal implicitly converts to
not_null<czstring>:void PrintLn( not_null<czstring> str ); int main() { PrintLn( "Hello, World!" ); }Qexplicitly converts tonot_null<P>ifQis nullable and implicitly converts toP, or ifQexplicitly converts toP.
For example:std::string readLine( not_null<std::FILE*> file ); std::FILE* file = ...; //readLine( file ); // does not compile: `file` is nullable readLine( not_null( file ) ); // compiles and executes a check at runtime- If
Pis hashable (that is, thestd::hash<P>specialization is enabled),not_null<P>is hashable.
not_null<P> is meant to point to single objects, not arrays of objects. It therefore does not define a subscript operator,
pointer increment or decrement operators, or pointer addition or subtraction operators.
gsl_lite::make_not_null()
For C++14 and older, where class template argument deduction
is not available, gsl-lite defines the helper function make_not_null() for explicitly constructing not_null<> objects. Example:
auto filePtr = FilePtr( std::fopen( ... ) );
if ( filePtr == nullptr ) throw std::runtime_error( ... );
auto file = FileHandle( make_not_null( std::move( filePtr ) ) );
gsl_lite::make_unique<>() and gsl_lite::make_shared<>()
gsl-lite defines the helper functions make_unique<T>() and make_shared<T>() which behave like
std::make_unique<T>() and
std::make_shared<T>() but return not_null<std::unique_ptr<T>>
and not_null<std::shared_ptr<T>>, respectively.
not_null<P> is meant to point to single objects, not arrays of objects, therefore gsl_lite::make_unique<T>() and
gsl_lite::make_shared<T>() are not defined for array types T.
gsl_lite::is_valid()
A not_null<P> cannot be directly compared to a nullptr because it is not meant to be nullable.
If you have to check for the moved-from state, use the gsl_lite::is_valid() predicate:
auto npi = gsl_lite::make_unique<int>( 42 );
// ...
//if ( npi == nullptr ) { ... } // compile error
if ( ! gsl_lite::is_valid( npi ) ) { ... } // ok
gsl_lite::as_nullable()
To extract the nullable object wrapped by a not_null<> object, call gsl_lite::as_nullable():
auto npi = gsl_lite::make_unique<int>( 42 ); // not_null<std::unique_ptr<int>>
auto pi = gsl_lite::as_nullable( std::move( npi ) ); // std::unique_ptr<int>
This is useful when accessing operations of P not forwarded by not_null<P>:
void insertAfter( not_null<ListNode *> x, not_null<std::unique_ptr<ListNode>> newNode )
{
newNode->prev = x;
newNode->next = x->next;
// no `not_null<>::release()` member function
x->next = gsl_lite::as_nullable( std::move( newNode ) ).release();
}
gsl_lite::get()
The raw pointer stored by a smart pointer can typically be extracted by calling the get() member function. not_null<P>
conditionally defines a get() member function which transparently forwards to P::get() if it exists. However, nullability
is not preserved when calling the get() member function:
auto pi = std::make_unique<int>( 42 ); // std::unique_ptr<int>
auto npi = gsl_lite::make_unique<int>( 42 ); // not_null<std::unique_ptr<int>>
auto rpi = pi.get(); // int*
auto rnpi = npi.get(); // int*
To preserve the nullability when extracting the pointer, the free function gsl_lite::get() may be used:
auto rpi = gsl_lite::get( pi ); // int*
auto rnpi = gsl_lit::get( npi ); // not_null<int*>
auto rpi2 = gsl_lite::get( rpi ); // int*
auto rnpi2 = gsl_lite::get( rnpi ); // not_null<int*>
Nullability and the moved-from state
For pointer types P which are trivially move-constructible,
a not_null<P> object is guaranteed to hold a pointer value that is different from nullptr.
This is different for custom pointer types such as std::unique_ptr<> which define a non-trivial move constructor. not_null<P>
retains the copyability and movability of P, and hence a not_null<P> object may have a moved-from state if the
underlying pointer P has one. A not_null<P> object may therefore be nullptr after it has been moved from:
auto x = gsl_lite::make_unique<int>( 42 ); // not_null<std::unique_ptr<int>>()
auto y = std::move( x ); // x is now nullptr
*x = 43; // dereferencing a nullptr ⇒ runtime contract violation
This is where gsl-lite’s implementation of not_null<> differs from the implementation of Microsoft GSL, which ensures that
its not_null<P> cannot ever become nullptr, rendering its not_null<std::unique_ptr<T>> immovable (cf. microsoft/GSL#1022).
While this choice would prevent the error above, it inhibits many interesting use cases for not_null<>.
For example, consider the following resource handle class:
struct FileCloser
{
void operator ()( std::FILE* file ) const noexcept
{
std::fclose( file );
}
};
using FilePtr = std::unique_ptr<std::FILE, FileCloser>;
class FileHandle
{
private:
FilePtr file_;
public:
FileHandle( FileHandle && rhs ) = default;
FileHandle & operator =( FileHandle && rhs ) = default;
explicit FileHandle( FilePtr _file )
: file_( std::move( _file ) )
{
}
int getc()
{
return std::fgetc( file_.get() );
}
...
};
To make FileHandle null-safe, we can add explicit precondition checks:
class FileHandle // movable
{
private:
FilePtr file_;
public:
FileHandle( FileHandle && rhs )
: file_( std::move( rhs.file_ ) )
{
gsl_Expects( file_ != nullptr );
}
FileHandle & operator =( FileHandle && rhs )
{
gsl_Expects( rhs.file_ != nullptr );
file_ = std::move(rhs.file_);
return *this;
}
explicit FileHandle( FilePtr _file )
: file_( std::move( _file ) )
{
gsl_Expects( file_ != nullptr );
}
int getc()
{
gsl_Expects( file_ != nullptr );
return std::fgetc( file_.get() );
}
...
};
This is very tedious, not least because we have to define move constructor and move assignment operator manually.
By using not_null<>, we can instead “lift” these preconditions to the type system and have all the
precondition checks be generated automatically:
class FileHandle // still movable
{
private:
not_null<FilePtr> file_; // <--
public:
// implicit precondition check `rhs.file_ != nullptr`
FileHandle( FileHandle && rhs ) = default;
// implicit precondition check `rhs.file_ != nullptr`
FileHandle & operator =( FileHandle && rhs ) = default;
explicit FileHandle( not_null<FilePtr> _file ) // <--
: file_( std::move( _file ) )
{
// implicit precondition check `_file != nullptr`
}
int getc()
{
// implicit precondition check `file_ != nullptr`
return std::fgetc( file_.get() );
}
...
};
Any code constructing a FileHandle will now have to explicitly cast the pointer as not_null:
auto filePtr = FilePtr( std::fopen( ... ) );
if ( filePtr == nullptr ) throw std::runtime_error( ... );
auto file = FileHandle( not_null( std::move( filePtr ) ) );
But any function accepting a FileHandle can now be sure that the object holds a valid pointer:
std::vector<std::string>
readLines( FileHandle file )
{
// file cannot hold a nullptr here
...
}
Although not_null<> can be used to inject preconditions and postconditions, it does not inject new
invariants. After being moved from, a FileHandle indeed holds a nullptr in its file_ pointer.
Therefore, non-nullability of the file_ pointer is not an invariant of the FileHandle class.
However, a FileHandle is still safe to use because of the implicit precondition checks injected by not_null<>.
Also, use-after-move errors can often be detected by static analysis tools, so this may be considered
“safe enough” for practical purposes.
not_null_ic<P>
(Note: not_null_ic<> is a gsl-lite extension and not part of the C++ Core Guidelines.)
gsl_lite::not_null_ic<P> is a class template that wraps a pointer type P while enforcing non-nullability.
It provides all the guarantees and run-time checks of not_null<P> but relaxes the requirements
of its conversion constructors: implicit conversion from U to not_null_ic<P> is allowed if U implicitly
converts to P.
not_null<> does not allow implicit conversion from nullable types:
void use( not_null<int *> p );
int i;
//use( &i ); // compile error: no implicit conversion
use( not_null( &i ) ); // runtime check
This choice has the generally desirable consequence that it encourages propagation of non-nullability.
Explicit conversions are needed only when converting a nullable pointer to a non-nullable pointer; therefore, as
more and more of a code base is converted to not_null<>, fewer explicit conversions need to be used.
However, in some codebases it may not be feasible to insert explicit not-null checks at every invocation
site. In such a situation, gsl_lite::not_null_ic<P> can be used instead. not_null_ic<P> derives from not_null<P>
but additionally allows implicit construction from nullable types:
void use( not_null_ic<int *> p );
int i;
use( &i ); // runtime check
(Compatibility note: Microsoft GSL defines the classes not_null<> and strict_not_null<> which behave
like gsl-lite’s not_null_ic<> and not_null<>, respectively.)
Numeric type conversions
(Core Guidelines reference: GSL.util: Utilities)
narrow<T>( u ), a checked numeric castnarrow_failfast<T>( u ), a checked numeric castnarrow_cast<T>( u ), an unchecked numeric cast
narrow<T>( u )
gsl_lite::narrow<T>( u ) is a numeric cast that is not meant to be lossy. If narrowing leads to a change of sign or
loss of information, an exception of type gsl_lite::narrowing_error is thrown.
Example:
double volume = ...; // (m³)
double bucketCapacity = ...; // (m³)
double numBucketsF = std::ceil( volume/bucketCapacity );
try
{
auto numBuckets = gsl_lite::narrow<int>( numBucketsF );
std::cout << "Number of buckets required: " << numBuckets;
fillBuckets( numBuckets );
}
catch ( gsl_lite::narrowing_error const & )
{
std::cerr << "This is more than I can handle.\n";
}
In this example, an exception will be thrown if numBucketsF is not an integer (for instance, std::ceil(-INFINITY) will return -INFINITY),
or if the value cannot be represented by int.
narrow_failfast<T>( u )
(Note: narrow_failfast<T>( u ) is a gsl-lite extension and not part of the C++ Core Guidelines.)
gsl_lite::narrow_failfast<T>( u ) is a numeric cast that is not meant to be lossy, which is verified with
gsl_Assert(). If narrowing leads to a change of sign or loss of information, an
assertion violation is triggered.
The narrow<T>( u ) function specified by the C++ Core Guidelines throws an exception, thereby indicating exceptional circumstances
(for instance, “input data too large”). The exception may be caught and dealt with at runtime. Contrariwise, the purpose of
narrow_failfast<T>( u ) is to detect programming errors which the user of the program cannot do anything about.
Example 1:
void printCmdArgs( gsl_lite::span<gsl_lite::zstring const> cmdArgs );
int main( int argc, char * argv[] )
{
auto args = gsl_lite::span(
argv,
// Something is seriously wrong if this cast fails.
gsl_lite::narrow_failfast<std::size_t>( argc ) );
printCmdArgs( args );
}
Example 2:
auto vec = std::vector{ 1, 2, 3 };
int elem = 2;
auto pos = std::find( vec.begin(), vec.end(), elem );
// Bug: we accidentally swapped `pos` and `vec.end()`.
auto delta = pos - vec.end();
// Assertion violation: `delta` is negative.
auto subrangeSize = gsl_lite::narrow_failfast<std::size_t>( delta );
auto subrange = std::vector<int>( subrangeSize );
narrow_cast<T>( u )
narrow_cast<T>( u ) is a numeric cast in which loss of information is acceptable. It is exactly equivalent to static_cast<T>( u ),
the only difference being that narrow_cast<>() conveys the intent that truncation or sign change is acceptable or even desired.
Example 1:
// Sign change to 0xFFFFFFFF
auto allBitsSet = gsl_lite::narrow_cast<std::uint32_t>( -1 );
Example 2:
int floor( float val )
{
gsl_Expects(
val >= std::numeric_limits<int>::lowest() &&
val <= std::numeric_limits<int>::max() );
return gsl_lite::narrow_cast<int>( val ); // truncation is desired here
}
Safe contiguous ranges
(Core Guidelines reference: GSL.view: Views)
gsl-lite defines a class gsl_lite::span<T, Extent> that represents a contiguous sequence of objects. The interface
of span<> is identical to that of std::span<>,
but all operations in gsl-lite’s span<> use gsl_Expects() to
check their preconditions at runtime. span<>::iterator also verifies the preconditions of all its operations
with gsl_ExpectsDebug().
gsl-lite also defines a set of helper functions make_span() for explicitly constructing span<> objects. This is useful
for type inference in C++14 and older where class template argument deduction
is not available. Example:
void printCmdArgs( gsl_lite::span<gsl_lite::zstring const> cmdArgs );
int main( int argc, char * argv[] )
{
auto args = gsl_lite::make_span(
argv, gsl_lite::narrow_failfast<std::size_t>( argc ) );
printCmdArgs( args );
}
Bounds-checked element access
(Core Guidelines reference: GSL.util: Utilities)
The function gsl_lite::at( container, index ) offers bounds-checked element access for all sized containers with random access.
Exposition-only definition:
template< class Container >
auto at( Container & c, index i )
{
gsl_Expects( i >= 0 && i < std::ssize( c ) );
return c[ i ];
}
Integer type aliases
(Core Guidelines reference: GSL.util: Utilities)
(Note: dim, stride, and diff are gsl-lite extensions and not part of the C++ Core Guidelines.)
Rule ES.107 of the C++ Core Guidelines suggests,
“Don’t use unsigned for subscripts, prefer gsl::index,” giving several good reasons for preferring a signed over an unsigned
type for indexing. For this purpose, the GSL defines index as a type alias for std::ptrdiff_t.
gsl-lite defines the type alias gsl_lite::index along with the aliases gsl_lite::dim, gsl_lite::stride, and gsl_lite::diff:
| Type alias | Purpose |
|---|---|
index |
Signed integer type for indexes and subscripts |
dim |
Signed integer type for sizes |
stride |
Signed integer type for index strides |
diff |
Signed integer type for index differences |
Example:
auto x = std::vector<double>{ ... };
auto dx = std::vector<double>( x.size() - 1 );
gsl_lite::dim n = std::ssize( x );
for ( gsl_lite::index i = 0; i < n - 1; ++i )
{
dx[ i ] = x[ i + 1 ] - x[ i ];
}
index, dim, stride, and diff are all aliases for std::ptrdiff_t unless the gsl_CONFIG_INDEX_TYPE
configuration macro is set to a different type (which is not recommended).
String type aliases
(Core Guidelines reference: GSL.view: Views)
(Note: wzstring, cwzstring, u8zstring, cu8zstring, u16zstring, cu16zstring, u32zstring, and cu32zstring
are a gsl-lite extension and not part of the C++ Core Guidelines.)
gsl-lite defines the aliases gsl_lite::zstring, gsl_lite::czstring, and others to represent C-style strings,
where a C-style string is understood to be either a zero-terminated sequence of characters or a nullptr:
| Type alias | Type |
|---|---|
zstring |
char * |
czstring |
char const * |
wzstring |
wchar_t * |
cwzstring |
wchar_t const * |
u8zstring |
char8_t * |
cu8zstring |
char8_t const * |
u16zstring |
char16_t * |
cu16zstring |
char16_t const * |
u32zstring |
char32_t * |
cu32zstring |
char32_t const * |
Non-nullable C-style strings
To represent a C-style string which must not be nullptr, the string aliases may be combined with not_null<>:
template <typename OutIt>
copy_z( not_null<czstring> in, OutIt out )
{
for ( czstring p = in; *p != '\0'; )
{
*out++ = *p++;
}
}
gsl_lite::c_str()
std::string and similar string types store a string with a trailing 0-terminator. The string can therefore be used
as a C-style string, which is returned by the c_str() member function. However, even though std::string::c_str() is
guaranteed not to return a nullptr, the return type is czstring. To preserve nullability when obtaining a C-style string,
the free function gsl_lite::c_str() may be used:
std::string str = "Hello, World";
auto cstr = str.c_str(); // czstring
auto ncstr = gsl_lite::c_str( str ); // not_null<czstring>
auto lit = "Hello, World!"; // czstring
auto nlit = gsl_lite::c_str( "Hello, World!" ); // not_null<czstring>
auto cstr2 = gsl_lite::c_str( cstr ); // czstring
auto ncstr2 = gsl_lite::c_str( ncstr ); // not_null<czstring>
Ad hoc resource management (C++11 and higher)
(Core Guidelines reference: GSL.util: Utilities)
(Note: on_return() and on_error() are gsl-lite extensions and not part of the C++ Core Guidelines.)
gsl-lite defines the following helpers for ad hoc resource management:
gsl_lite::finally( action )constructs and returns an object which invokesactionupon destruction.gsl_lite::on_return( action )constructs and returns an object which invokesactionupon destruction only if no exception was thrown.gsl_lite::on_error( action )constructs and returns an object which invokesactionupon destruction only if an exception was thrown.
Rule R.1 of the C++ Core Guidelines suggests: “Manage resources automatically using resource handles and RAII (Resource Acquisition Is Initialization)”. While this advice is sound, it may sometimes be inconvenient to always define a resource handle type for every situation in which a resource needs to be cleaned up.
For example, we might wish to modernize the following code which uses the C standard library to read from a file:
std::vector<std::string> readLines( char const * filename )
{
std::FILE * file = std::fopen( filename, "r" );
if ( !file ) throw std::runtime_error( ... );
std::vector<std::string> result;
... // implementation omitted
std::fclose( file );
return result;
}
This code is not exception-safe: if the (omitted) implementation throws an exception, fclose() is never called on the file handle.
The problem of exception safety is typically addressed by defining a resource handle type for FILE
(see the FileHandle example above):
struct FileCloser
{
void operator ()(std::FILE* file) const noexcept
{
std::fclose( file );
}
};
using FilePtr = std::unique_ptr<std::FILE, FileCloser>;
std::vector<std::string> readLines( char const * filename )
{
auto file = FilePtr( std::fopen( filename, "r" ) );
if ( !file ) throw std::runtime_error( ... );
std::vector<std::string> result;
... // implementation omitted
return result;
}
Alternatively, we can fix the problem by using gsl_lite::finally():
std::vector<std::string> readLines( char const * filename )
{
std::FILE * file = std::fopen( filename, "r" );
if ( !file ) throw std::runtime_error( ... );
auto _ = gsl_lite::finally( [&] { std::fclose( file ); } );
std::vector<std::string> result;
... // implementation omitted
return result;
}
The destructor of the local object _ will call std::fclose( file ) regardless of whether the function returns normally or is
interrupted by an exception. This ensures that the file handle does not leak.
Feature checking macros
(Note: Feature checking macros are a gsl-lite extension and not part of the C++ Core Guidelines.)
The following preprocessor macros can be used to identify features of the C++ build environment:
| Name | Meaning |
|---|---|
| Metadata: | |
gsl_lite_MAJOR |
Major version number of gsl-lite |
gsl_lite_MINOR |
Minor version number of gsl-lite |
gsl_lite_PATCH |
Patch version number of gsl-lite |
gsl_lite_VERSION |
A string holding the semantic version number <major>.<minor>.<patch> of gsl-lite (e.g. "1.0.0") |
| Language and library support: | |
gsl_CPPxx_OR_GREATER |
Whether C++xx language features are available (substitute 11, 14, 17, 20, 23, 26) |
gsl_STDLIB_CPPxx_OR_GREATER |
Whether C++xx standard library features are available (substitute 11, 14, 17, 20, 23, 26) |
| Compiler version detection: | |
gsl_BETWEEN( V, L, H ) |
V ≥ L and V < H |
gsl_COMPILER_GNUC_VERSION |
Evaluates to GCC version number when compiled with GNU GCC, 0 otherwise |
gsl_COMPILER_CLANG_VERSION |
Evaluates to Clang version number when compiled with Clang, 0 otherwise |
gsl_COMPILER_MSVC_VERSION |
Evaluates to MSVC version number when compiled with Microsoft Visual C++, 0 otherwise |
gsl_COMPILER_MS_STL_VERSION |
Evaluates to MSVC version number when compiled with Microsoft Visual C++ or Clang with Microsoft STL, 0 otherwise |
gsl_COMPILER_APPLECLANG_VERSION |
Evaluates to AppleClang version number when compiled with Apple Clang, 0 otherwise |
gsl_COMPILER_NVCC_VERSION |
Evaluates to NVCC version number when compiled with NVIDIA NVCC, 0 otherwise |
gsl_COMPILER_ARMCC_VERSION |
Evaluates to ARMCC version number when compiled with ARMCC, 0 otherwise |
gsl_DEVICE_CODE |
Whether CUDA device code is being compiled |
gsl_HAVE( EXCEPTIONS ) |
Evaluates to 1 if exceptions are available, 0 when compiling with exceptions disabled |
gsl_HAVE( WCHAR ) |
Evaluates to 1 if wchar_t type is available, 0 otherwise |
When a new revision of the C++ language is standardized, features often become available gradually in compilers and standard libraries.
C++20 introduces a set of preprocessor macros to check for the availability of language features
and standard library features; but such macros are not necessarily available in
implementations predating C++20. For this purpose, gsl-lite defines a limited set of feature checking macros.
They all follow the pattern gsl_HAVE( xx ), where xx is to be replaced by a token representing a given language feature. gsl_HAVE( xx ) evaluates
to 1 if the corresponding language or library feature is available, 0 otherwise.
| Name | Feature |
|---|---|
| Language features: | |
gsl_HAVE( C99_PREPROCESSOR ) |
C99-compatible preprocessor |
gsl_HAVE( AUTO ) |
auto (C++11) |
gsl_HAVE( RVALUE_REFERENCE ) |
rvalue references (C++11) |
gsl_HAVE( FUNCTION_REF_QUALIFIER ) |
ref-qualified member functions (C++11) |
gsl_HAVE( ENUM_CLASS ) |
enum class (C++11) |
gsl_HAVE( ALIAS_TEMPLATE ) |
alias templates (C++11) |
gsl_HAVE( DEFAULT_FUNCTION_TEMPLATE_ARG ) |
default template arguments for function templates (C++11) |
gsl_HAVE( EXPLICIT ) |
explicit specifier (C++11) |
gsl_HAVE( VARIADIC_TEMPLATE ) |
variadic templates (C++11) |
gsl_HAVE( IS_DELETE ) |
deleted functions (C++11) |
gsl_HAVE( IS_DEFAULT ) |
explicitly defaulted functions (C++11) |
gsl_HAVE( NOEXCEPT ) |
noexcept specifier and noexcept() operator (C++11) |
gsl_HAVE( NORETURN ) |
[[noreturn]] attribute (C++11) |
gsl_HAVE( EXPRESSION_SFINAE ) |
expression SFINAE |
gsl_HAVE( OVERRIDE_FINAL ) |
override and final specifiers (C++11) |
gsl_HAVE( DECLTYPE_AUTO ) |
decltype(auto) (C++11) |
gsl_HAVE( DEPRECATED ) |
[[deprecated]] attribute (C++11) |
gsl_HAVE( ENUM_CLASS_CONSTRUCTION_FROM_UNDERLYING_TYPE ) |
constructing enum class from the underlying type (C++17) |
gsl_HAVE( DEDUCTION_GUIDES ) |
class template argument deduction guides (C++17) |
gsl_HAVE( NODISCARD ) |
[[nodiscard]] attribute (C++17) |
gsl_HAVE( MAYBE_UNUSED ) |
[[maybe_unused]] attribute (C++17) |
gsl_HAVE( CONSTEXPR_xx ) |
C++xx constexpr features (substitute 11, 14, 17, 20, 23, 26) |
| Standard library features: | |
gsl_HAVE( ADDRESSOF ) |
std::addressof() (C++11) |
gsl_HAVE( ARRAY ) |
std::array<> (C++11) |
gsl_HAVE( TYPE_TRAITS ) |
<type_traits> header (C++11) |
gsl_HAVE( CONTAINER_DATA_METHOD ) |
data() member function on containers |
gsl_HAVE( STD_DATA ) |
std::data() (C++17) |
gsl_HAVE( STD_SSIZE ) |
std::ssize() (C++20) |
gsl_HAVE( HASH ) |
std::hash<> (C++11) |
gsl_HAVE( SIZED_TYPES ) |
sized integer type aliases (C++11) |
gsl_HAVE( SHARED_PTR ) |
std::shared_ptr<> (C++11) |
gsl_HAVE( UNIQUE_PTR ) |
std::unique_ptr<> (C++11) |
gsl_HAVE( MAKE_SHARED ) |
std::make_shared<>() (C++11) |
gsl_HAVE( MAKE_UNIQUE ) |
std::make_unique<>() (C++14) |
gsl_HAVE( MOVE_FORWARD ) |
std::move() and std::forward<>() (C++11) |
gsl_HAVE( NULLPTR ) |
nullptr keyword (C++11) |
gsl_HAVE( UNCAUGHT_EXCEPTIONS ) |
std::uncaught_exceptions() (C++17) |
gsl_HAVE( INITIALIZER_LIST ) |
std::initializer_list<> (C++11) |
gsl_HAVE( REMOVE_CVREF ) |
std::remove_cvref<> (C++20) |
Polyfills
(Note: Polyfills are a gsl-lite extension and not part of the C++ Core Guidelines.)
gsl-lite defines some macros, types, and functions for use with earlier versions of C++:
Keyword and attribute macros
The keyword and attribute macros allow to conditionally take advantage of newer language features if available:
| Name | Expands to |
|---|---|
gsl_DIMENSION_OF(a) |
( sizeof( a ) / sizeof( 0[ a ] ) ), which is the number of elements in a C-style array a |
gsl_constexpr |
constexpr in C++11 and higher, to nothing otherwise |
gsl_constexprXX |
constexpr in C++XX and higher, to nothing otherwise(substitute 14, 17, 20, 23, 26) |
gsl_explicit |
explicit specifier in C++11 and higher, to nothing otherwise |
gsl_is_delete |
= delete in C++11 and higher, to nothing otherwise |
gsl_is_delete_access |
public in C++11 and higher, to private otherwise |
gsl_noexcept |
noexcept specifier in C++11 and higher, to nothing otherwise |
gsl_noexcept_if(expr) |
noexcept( expr ) operator in C++11 and higher, to nothing otherwise |
gsl_nullptr |
nullptr in C++11 and higher, to NULL otherwise |
gsl_NORETURN |
[[noreturn]] attribute in C++11 and higher, to a compiler-specific attribute if available, or to nothing otherwise |
gsl_DEPRECATED |
[[deprecated]] attribute in C++14 and higher, to nothing otherwise |
gsl_DEPRECATED_MSG(msg) |
[[deprecated( msg )]] attribute in C++14 and higher, to nothing otherwise |
gsl_NODISCARD |
[[nodiscard]] attribute in C++17 and higher, to nothing otherwise |
gsl_MAYBE_UNUSED |
[[maybe_unused]] attribute in C++17 and higher, or to nothing otherwise |
gsl_MAYBE_UNUSED_MEMBER |
[[maybe_unused]] attribute in C++17 and higher if that attribute does not raise a warning when applied to class data members (as is the case for GNU GCC), or to nothing otherwise |
gsl_NO_UNIQUE_ADDRESS(≥ C++20) |
[[msvc::no_unique_address]] for MSVC, to [[no_unique_address]] otherwise |
Code generation macros
The following macros help avoid writing repetitive code:
gsl_DEFINE_ENUM_BITMASK_OPERATORS( e ):
Defines bitmask operators|,&,^,~,|=,&=, and^=for the enum typee.
Example:enum class Vegetables { tomato = 0b001, onion = 0b010, eggplant = 0b100 }; gsl_DEFINE_ENUM_BITMASK_OPERATORS( Vegetables )Some explicit Boolean conversions are supported to simplify bitflag checking:
Vegetables ingredients = ...; if ( ! ingredients ) { ... } else if ( ingredients & Vegetables::tomato ) { ... }gsl_DEFINE_ENUM_RELATIONAL_OPERATORS( e ):
Defines relational operators (<=>in C++20 and newer,<,>,<=, and>=otherwise) for the enum typee.
Example:enum class OperatorPrecedence { additive = 0, multiplicative = 1, power = 2 }; gsl_DEFINE_ENUM_RELATIONAL_OPERATORS( OperatorPrecedence )This code generation macro can save a lot of boilerplate in pre-C++20 code, but in C++20 it is almost unnecessary because only
operator <=>must be defined:[[nodiscard]] constexpr auto operator<=>( OperatorPrecedence lhs, OperatorPrecedence rhs ) noexcept { return gsl_lite::to_underlying( lhs ) <=> gsl_lite::to_underlying( rhs ); }
Types and functions
The following types and functions implement some functionality added only in later C++ standards:
| Name | C++ feature |
|---|---|
gsl_lite::std11::add_const<> |
std::add_const<> (C++11) |
gsl_lite::std11::remove_const<>std11::remove_volatile<>std11::remove_cv<> |
std::remove_const<> (C++11)std::remove_volatile<> (C++11)std::remove_cv<> (C++11) |
gsl_lite::std11::remove_reference<> |
std::remove_reference<> (C++11) |
gsl_lite::std11::integral_constant<>gsl_lite::std11::true_typegsl_lite::std11::false_typegsl_lite::std17::bool_constant<> |
std::integral_constant<> (C++11)std::true_type (C++11)std::false_type (C++11)std::bool_constant<> (C++17) |
gsl_lite::std14::make_unique<>() |
std::make_unique<>() (C++14) |
gsl_lite::std17::uncaught_exceptions() |
std::uncaught_exceptions() (C++17) |
gsl_lite::std17::negation<> |
std::negation<> (C++17) |
gsl_lite::std17::conjunction<> (≥ C++11) |
std::conjunction<> (C++17) |
gsl_lite::std17::disjunction<> (≥ C++11) |
std::disjunction<> (C++17) |
gsl_lite::std17::void_t<> (≥ C++11) |
std::void_t<> (C++17) |
gsl_lite::size(), gsl_lite::std17::size()gsl_lite::ssize(), gsl_lite::std20::ssize() |
std::size() (C++17)std::ssize() (C++20) |
gsl_lite::data(), gsl_lite::std17::data() |
std::data() (C++17) |
gsl_lite::std20::endian |
std::endian (C++20) |
gsl_lite::type_identity<>, gsl_lite::std20::type_identity<> |
std::type_identity<> (C++20) |
gsl_lite::identity, gsl_lite::std20::identity |
std::identity (C++20) |
gsl_lite::std20::remove_cvref<> |
std::remove_cvref<> (C++20) |
gsl_lite::to_underlying(), gsl_lite::std23::to_underlying() |
std::to_underlying() (C++23) |
Configuration options and switches
gsl-lite is customizable through a large number of configuration options and switches. The configuration of contract checks may be useful for various purposes (performance, unit testing, fail-safe environments). The main purpose of the other configuration options is backward compatibility.
The configuration macros may affect the API and ABI of gsl-lite in ways that renders it incompatible with other code. Therefore, as a general rule, do not define, or rely on, any of gsl-lite’s configuration options or switches when using gsl-lite in a library.
Contract checking configuration macros
With the configuration macros described in the following sections, the user can exert fine-grained control over the runtime behavior
of contract checks expressed with gsl_Expects(), gsl_Ensures(), gsl_Assert() and other contract checking macros.
The configuration options for contract violation response follow the suggestions originally suggested in proposal N4415,
with some refinements inspired by P1710/P1730.
Runtime enforcement
The following macros control whether contracts are checked at runtime:
-
gsl_CONFIG_CONTRACT_CHECKING_AUDIT
Define this macro to have contracts expressed withgsl_ExpectsAudit(),gsl_EnsuresAudit(), andgsl_AssertAudit()checked at runtime. -
gsl_CONFIG_CONTRACT_CHECKING_ON(default)
Define this macro to have contracts expressed withgsl_Expects(),gsl_Ensures(),gsl_Assert(), andgsl_FailFast()checked at runtime, Contracts expressed withgsl_ExpectsDebug(),gsl_EnsuresDebug(), andgsl_AssertDebug()are also checked at runtime (unlessNDEBUGis defined andgsl_CONFIG_CONTRACT_CHECKING_AUDITis not).
This is the default. -
NDEBUGThis macro traditionally disables runtime checks for theassert()macro from the C standard library. Additionally, contracts expressed withgsl_ExpectsDebug(),gsl_EnsuresDebug(), andgsl_AssertDebug()are not evaluated or checked at runtime ifNDEBUGis defined andgsl_CONFIG_CONTRACT_CHECKING_AUDITis not. -
gsl_CONFIG_CONTRACT_CHECKING_OFF
Define this macro to disable all runtime checking of contracts and invariants.Note that
gsl_FailFast()checks will trigger runtime failure even if runtime checking is disabled. -
If desired, the macros
gsl_CONFIG_DEVICE_CONTRACT_CHECKING_AUDIT,gsl_CONFIG_DEVICE_CONTRACT_CHECKING_ON, andgsl_CONFIG_DEVICE_CONTRACT_CHECKING_OFFcan be used to configure contract checking for CUDA device code separately. If neither of these macros is defined, device code uses the same configuration as host code.It may be reasonable to define
gsl_CONFIG_DEVICE_CONTRACT_CHECKING_OFFin Release builds because the performance impact of runtime checks can be grave in device code, while it is often negligible in host code.
The following macros can be used to selectively disable checking for a particular kind of contract:
-
gsl_CONFIG_CONTRACT_CHECKING_EXPECTS_OFF
Define this macro to disable runtime checking of precondition contracts expressed withgsl_Expects(),gsl_ExpectsDebug(), andgsl_ExpectsAudit(). -
gsl_CONFIG_CONTRACT_CHECKING_ENSURES_OFF
Define this macro to disable runtime checking of postcondition contracts expressed withgsl_Ensures(),gsl_EnsuresDebug(), andgsl_EnsuresAudit(). -
gsl_CONFIG_CONTRACT_CHECKING_ASSERT_OFF
Define this macro to disable runtime checking of assertions expressed withgsl_Assert(),gsl_AssertDebug(), andgsl_AssertAudit().
Contract violation handling
The following macros control the handling of runtime contract violations:
-
gsl_CONFIG_CONTRACT_VIOLATION_ASSERTS(default)
If this macro is defined, contract assertions are handled with the assertion handler of the C++ runtime library. This is usually the most convenient choice. The exact behavior can be controlled with the configuration macrogsl_CONFIG_USE_CRT_ASSERTION_HANDLER. This is the default.This is the preferred option because, for most C++ runtime libraries, the default assertion handler reports diagnostic information (the current source file, line number, function name, and the assertion expression) in a way that is convenient for the programmer. For instance, with the Visual C++ Debug runtime on Microsoft Windows, the assertion mechanism displays an error message dialog which permits breaking into the debugger or continuing execution.
Note that
gsl_FailFast()will callstd::abort()if the assertion handler continues execution. -
gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES
Define this macro to callstd::terminate()on a contract violation. -
gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES_WITH_STACKTRACE(C++23)
Define this macro to print a stacktrace on a contract violation beforestd::terminate()is called.
Requires C++23 for the<stacktrace>functionality. -
gsl_CONFIG_CONTRACT_VIOLATION_TRAPS
Define this macro to execute a trap instruction on a contract violation.Trap instructions may yield smaller codegen and can thus result in better-performing code. However, they usually lead to catastrophic failure and may be difficult to diagnose for some platforms.
-
gsl_CONFIG_CONTRACT_VIOLATION_THROWS
Define this macro to throw astd::runtime_error-derived exceptiongsl_lite::fail_faston contract violation.
Handling contract violations with exceptions can be desirable when executing in an interactive programming environment, or if there are other reasons why process termination must be avoided.This setting is also useful when writing unit tests for contract checks.
gsl_CONFIG_CONTRACT_VIOLATION_CALLS_HANDLER
Define this macro to call a user-defined handler functiongsl_lite::fail_fast_assert_handler()on a contract violation. The user must provide a definition of the following function:namespace gsl_lite { gsl_api void fail_fast_assert_handler( char const * expression, char const * message, char const * file, int line ); } // namespace gsl_liteNote that
gsl_FailFast()will callstd::terminate()iffail_fast_assert_handler()returns.- If desired, the macros
gsl_CONFIG_DEVICE_CONTRACT_VIOLATION_ASSERTS,gsl_CONFIG_DEVICE_CONTRACT_VIOLATION_TRAPS, andgsl_CONFIG_DEVICE_CONTRACT_VIOLATION_CALLS_HANDLERcan be used to configure contract violation handling for CUDA device code separately. If neither of these macros is defined, device code uses the following defaults:gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES→gsl_CONFIG_DEVICE_CONTRACT_VIOLATION_ASSERTSgsl_CONFIG_CONTRACT_VIOLATION_ASSERTS→gsl_CONFIG_DEVICE_CONTRACT_VIOLATION_ASSERTSgsl_CONFIG_CONTRACT_VIOLATION_THROWS→gsl_CONFIG_DEVICE_CONTRACT_VIOLATION_ASSERTSgsl_CONFIG_CONTRACT_VIOLATION_TRAPS→gsl_CONFIG_DEVICE_CONTRACT_VIOLATION_TRAPSgsl_CONFIG_CONTRACT_VIOLATION_CALLS_HANDLER→gsl_CONFIG_DEVICE_CONTRACT_VIOLATION_CALLS_HANDLER
Unenforced contract checks
The following macros control what happens with individual contract checks which are not enforced at runtime. Note that these
macros do not disable runtime contract checking; they only configure what happens to contracts which are not checked as a result
of configuration, e.g. for any contract check if gsl_CONFIG_CONTRACT_CHECKING_OFF is defined, or for audit-level and debug-level
contract checks if NDEBUG is defined.
-
gsl_CONFIG_UNENFORCED_CONTRACTS_ELIDE(default)
Contract checks disabled by configuration will be discarded.
This is the default.Note that
gsl_FailFast()calls are never discarded.Even for discarded contract checks, gsl-lite will by default still verify that the contract check forms a valid Boolean expression using the C++11 features
decltype()andstatic_assert(). This may lead to problems if the contract check expression cannot be used in an unevaluated context, for instance, when using a lambda expression in C++11/14/17.The compile-time verification of contract check expressions is controlled by the configuration macro
gsl_CONFIG_VALIDATES_UNENFORCED_CONTRACT_EXPRESSIONS, which defaults to1. To suppress the verification, definegsl_CONFIG_VALIDATES_UNENFORCED_CONTRACT_EXPRESSIONS=0. -
gsl_CONFIG_UNENFORCED_CONTRACTS_ASSUME
For contracts expressed withgsl_Expects(),gsl_Ensures(), andgsl_Assert()which are not checked as a result of configuration, instruct the compiler to assume that they always hold true. This is expressed with compiler-specific intrinsics such as__assume().Contract checks expressed with
gsl_ExpectsDebug(),gsl_EnsuresDebug(),gsl_AssertDebug(),gsl_ExpectsAudit(),gsl_EnsuresAudit(), andgsl_AssertAudit()which are not checked at runtime (due to definition ofNDEBUGor one of the aforementioned configuration macros) are discarded.Explicitly injecting the assumption that contracts hold true implies that violating contracts causes undefined behavior. This may give the compiler more opportunities for optimization, but it is usually dangerous and, like all occurrences of undefined behavior, it can have devastating consequences.
The use of compiler-specific “assume” intrinsics may lead to spurious runtime evaluation of contract expressions. Because gsl-lite implements contract checks with macros (rather than as a language feature as the defunct C++2a Contracts proposal did), it cannot reliably suppress runtime evaluation for all compilers. For instance, if the contract check fed to the “assume” intrinsic comprises a function call which is opaque to the compiler, many compilers will generate the runtime function call. Therefore,
gsl_Expects(),gsl_Ensures(), andgsl_Assert()should be used only for conditions that can be proven side-effect-free by the compiler, andgsl_ExpectsDebug(),gsl_EnsuresDebug(),gsl_AssertDebug(),gsl_ExpectsAudit(),gsl_EnsuresAudit(), andgsl_AssertAudit()for everything else. In practice, this means thatgsl_Expects(),gsl_Ensures(), andgsl_Assert()should only be used for simple comparisons of scalar values, for simple inlineable getters, and for comparisons of class objects with trivially inlineable comparison operators.Revisiting the example given above:
template< class RandomIt > auto median( RandomIt first, RandomIt last ) { // Comparing iterators for equality boils down to a comparison of pointers. An optimizing // compiler will inline the comparison operator and understand that the comparison is free of // side-effects, and hence generate no code in `gsl_CONFIG_UNENFORCED_CONTRACTS_ASSUME` mode. gsl_Expects( first != last ); // If we cannot trust the compiler to understand that this function call is free of // side-effects, we should use `gsl_ExpectsDebug()` or `gsl_ExpectsAudit()`. This particular // function call is expensive, so we use an audit-level contract check. gsl_ExpectsAudit( std::is_sorted( first, last ) ); auto count = last - first; return count % 2 != 0 ? first[ count / 2 ] : std::midpoint( first[ count / 2 ], first[ count / 2 + 1 ] ); } -
If desired, the macros
gsl_CONFIG_DEVICE_UNENFORCED_CONTRACTS_ELIDEandgsl_CONFIG_DEVICE_UNENFORCED_CONTRACTS_ASSUMEcan be used to configure handling of unenforced contract checks for CUDA device code separately. If neither of these macros is defined, device code uses the same configuration as host code.
Feature selection macros
gsl_FEATURE_GSL_COMPATIBILITY_MODE=0
To minimize the impact of the breaking changes, gsl-lite v1.0 introduces an optional GSL compatibility mode controlled by the new configuration switch
gsl_FEATURE_GSL_COMPATIBILITY_MODE, which is is disabled by default and can be enabled by defining gsl_FEATURE_GSL_COMPATIBILITY_MODE=1.
Default is 0.
If the GSL compatibility mode is enabled, gsl-lite additionally makes the following global definitions:
namespace gsl = ::gsl_lite;
#define Expects( x ) gsl_Expects( x )
#define Ensures( x ) gsl_Ensures( x )
The GSL compatibility mode precludes the use of gsl-lite and Microsoft GSL in the same translation unit. Therefore, when making use of gsl-lite in a public header file of a library, the GSL compatibility mode should not be enabled.
The GSL compatibility mode causes no link-time interference between gsl-lite and as Microsoft GSL. Both libraries may be used in the same project as long as no translation unit includes both at the same time.
The legacy header file <gsl/gsl-lite.hpp> now forwards to <gsl-lite/gsl-lite.hpp> and implicitly enables the GSL compatibility mode. When the legacy
header is included, it emits a warning message which urges to either migrate to header <gsl-lite/gsl-lite.hpp>, namespace gsl_lite and the prefixed
contract checking macros gsl_Expects() and gsl_Ensures(), or to explicitly request GSL compatibility by defining gsl_FEATURE_GSL_COMPATIBILITY_MODE=1.
gsl_FEATURE_EXPERIMENTAL_RETURN_GUARD=0
Provide experimental resource management helper functions on_return() and on_error().
Default is 0.
gsl_FEATURE_STRING_SPAN=0
String spans and related functionality are no longer part of the GSL specification. If the macro gsl_FEATURE_STRING_SPAN is set to 1, gsl-lite
continues to provide an implementation of the class basic_string_span<> along with the aliases string_span, cstring_span, wstring_span, cwstring_span,
the deprecated class basic_zstring_span<> with the aliases zstring_span, czstring_span, wzstring_span, cwzstring_span, and related classes and
functions such as to_string(), and ensure_z().
Default is 0.
gsl_FEATURE_SPAN=1
Controls whether gsl_lite::span<> and related functions such as make_span() are defined.
Although C++ has introduced std::span<>, span<> from the C++ Core Guidelines Support Library
has not been deprecated because, unlike std::span<>, it checks for out-of-bounds accesses at runtime. However, C++26 will introduce
standard library hardening which adds limited support for
bounds checks to std::span<>, addressing most of the concerns voiced by the editors of the Core Guidelines. gsl-lite still defines the span<> class
by default, but it can be disabled by setting gsl_FEATURE_SPAN=0.
Default is 1.
gsl_FEATURE_BYTE=0
The byte type has been superseded by std::byte in C++17 and thus is no longer part of the GSL specification.
If the macro gsl_FEATURE_BYTE is set to 1, gsl-lite continues to provide an implementation of byte and related functions such as as_bytes(), to_byte(),
as_bytes(), and as_writable_bytes().
Default is 0.
gsl_FEATURE_WITH_CONTAINER_TO_STD=0
Define this to the highest C++ standard (98, 3, 11, 14, 17, 20) you want to include tagged-construction via with_container, or 0 to disable the feature.
Default is 0.
gsl_FEATURE_MAKE_SPAN_TO_STD=99
Define this to the highest C++ standard (98, 3, 11, 14, 17, 20) you want to include make_span() creator functions, or 0 to disable the feature.
Default is 99 for inclusion with any standard.
gsl_FEATURE_BYTE_SPAN_TO_STD=99
Define this to the highest C++ standard (98, 3, 11, 14, 17, 20) you want to include byte_span() creator functions, or 0 to disable the feature.
Default is 99 for inclusion with any standard.
Other configuration macros
gsl_CONFIG_ACKNOWLEDGE_NONSTANDARD_ABI=0
Define this to 1 to explicitly acknowledge that you are using gsl-lite with a non-standard ABI and that you control the build flags of all components linked into your target.
Default is 0.
gsl_api
Functions in gsl-lite are decorated with gsl_api where appropriate. Define this macro to specify your own function decoration.
By default gsl_api is defined empty for non-CUDA platforms and __host__ __device__ for the CUDA platform.
Note: When a custom gsl_api macro is defined, gsl-lite emits a warning to notify the programmer that this alters the binary interface of gsl-lite, leading to possible ODR violations.
The warning can be explicitly overridden by defining gsl_CONFIG_ACKNOWLEDGE_NONSTANDARD_ABI=1.
gsl_CONFIG_DEFAULTS_VERSION=1
Define this macro to 0 to revert the default configuration to that of gsl-lite v0.*. Cf. Configuration changes for a comprehensive list of configuration
values affected by this switch.
Default is 1 for version-1 defaults.
Note: Defining gsl_CONFIG_DEFAULTS_VERSION=0 changes the default value of gsl_CONFIG_INDEX_TYPE.
This makes gsl-lite emit a warning to notify the programmer that this alters the binary interface of gsl-lite, leading to possible ODR violations.
The warning can be explicitly overridden by defining gsl_CONFIG_ACKNOWLEDGE_NONSTANDARD_ABI=1.
gsl_CPLUSPLUS
Define this macro to override the auto-detection of the supported C++ standard if your compiler does not set the __cplusplus macro correctly.
gsl_CONFIG_DEPRECATE_TO_LEVEL=9
Define this to and including the level you want deprecation; see table Deprecated features below.
Default is 9.
gsl_CONFIG_SPAN_INDEX_TYPE=std::size_t
Define this macro to the type to use for indices in span<> and basic_string_span<>.
Default is std::size_t.
Note: When a custom span index type is defined, gsl-lite emits a warning to notify the programmer that this alters the binary interface of gsl-lite, leading to possible ODR violations.
The warning can be explicitly overridden by defining gsl_CONFIG_ACKNOWLEDGE_NONSTANDARD_ABI=1.
gsl_CONFIG_INDEX_TYPE=std::ptrdiff_t
Define this macro to the type to use for gsl_lite::index, gsl_lite::dim, gsl_lite::stride, and gsl_lite::diff.
Default is std::ptrdiff_t.
Note: When a custom index type is defined, gsl-lite emits a warning to notify the programmer that this alters the binary interface of gsl-lite, leading to possible ODR violations.
The warning can be explicitly overridden by defining gsl_CONFIG_ACKNOWLEDGE_NONSTANDARD_ABI=1.
gsl_CONFIG_NOT_NULL_EXPLICIT_CTOR=1
Define this macro to 0 to make not_null<>’s constructor implicit.
Default is 1.
Preferably, rather than defining this macro to 0, use not_null_ic<> if you desire implicit construction.
gsl_CONFIG_TRANSPARENT_NOT_NULL=1
If this macro is defined to 1, not_null<> supports typical member functions of the underlying smart pointer transparently (currently get()), while adding precondition checks.
This is conformant behavior but may be incompatible with older code which expects that not_null<>::get() returns the underlying pointer itself.
Default is 1.
gsl_CONFIG_NOT_NULL_GET_BY_CONST_REF=0
Define this macro to 1 to have the legacy non-transparent version of not_null<>::get() return T const & instead of T. This may improve performance with types that have an expensive copy-constructor.
This macro must not be defined if gsl_CONFIG_TRANSPARENT_NOT_NULL is 1.
Default is 0 for T.
gsl_CONFIG_ALLOWS_SPAN_COMPARISON=0
Define this macro to 1 to support equality comparison and relational comparison of spans. C++20 std::span<> does not support comparison because semantics (deep vs. shallow) are unclear.
Default is 0.
gsl_CONFIG_ALLOWS_NONSTRICT_SPAN_COMPARISON=0
Define this macro to 1 to support equality comparison and relational comparison of spans of different types, e.g. of different const-volatile-ness. To be able to compare a string_span with a cstring_span, non-strict span comparison must be available.
Default is 0.
gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR=0
Define this macro to 1 to add the unconstrained span constructor for containers for pre-C++11 compilers that cannot constrain the constructor. This constructor may prove too greedy and interfere with other constructors.
Default is 0.
Note: An alternative is to use the constructor tagged with_container: span<V> s(gsl_lite::with_container, cont).
gsl_CONFIG_NARROW_THROWS_ON_TRUNCATION=1
If this macro is 1, narrow<>() always throws a narrowing_error exception if the narrowing conversion loses information due to truncation.
If gsl_CONFIG_NARROW_THROWS_ON_TRUNCATION is 0 and gsl_CONFIG_CONTRACT_VIOLATION_THROWS is not defined, narrow<>() instead terminates on
information loss (using std::terminate() if available and a trap instruction otherwise, e.g. for CUDA device code).
Default is 1.
Note: When gsl_CONFIG_NARROW_THROWS_ON_TRUNCATION is defined as 0, gsl-lite emits a warning to notify the programmer that this may lead to possible ODR violations.
The warning can be explicitly overridden by defining gsl_CONFIG_ACKNOWLEDGE_NONSTANDARD_ABI=1.
gsl_CONFIG_CONFIRMS_COMPILATION_ERRORS=0
Define this macro to 1 to experience the by-design compile-time errors of the GSL components in the test suite.
Default is 0.
gsl_CONFIG_USE_CRT_ASSERTION_HANDLER
This macro controls how contract violations are handled if gsl_CONFIG_CONTRACT_VIOLATION_ASSERTS is defined.
If set to 1, contract violations are handled by the assertion handler of the C runtime:
- MSVC, Debug build:
_CrtDbgReport() - MSVC non-Debug build:
_assert() - Linux:
__assert_fail() - Other systems:
__assert()(widely available but undocumented)
If set to 0, contracts are handled with a custom assertion handler that behaves like the CRT assertion handler on most platforms (that is,
it prints an assertion message to stderr and terminates the program through std::abort()).
The assertion handler is called regardless of whether or not NDEBUG is defined. To suppress contract check assertions in a Release
build, define gsl_CONFIG_CONTRACT_CHECKING_OFF for the build configuration.
Default is 1 when building with MSVC or for Linux targets, where a CRT assertion handler is guaranteed to be available, and 0 otherwise.
Configuration changes, deprecated and removed features
Configuration changes
gsl-lite v1.0 changed the default values for several configuration options and switches:
-
gsl_FEATURE_STRING_SPAN:
Version-1 default:gsl_FEATURE_STRING_SPAN=0
Version-0 default:gsl_FEATURE_STRING_SPAN=1
Reason: string spans are no longer part of the GSL specification. -
gsl_FEATURE_BYTE:
Version-1 default:gsl_FEATURE_BYTE=0
Version-0 default:gsl_FEATURE_BYTE=1
Reason:bytehas been superseded bystd::bytein C++17. -
gsl_CONFIG_DEPRECATE_TO_LEVEL:
Version-1 default:gsl_CONFIG_DEPRECATE_TO_LEVEL=9Version-0 default:gsl_CONFIG_DEPRECATE_TO_LEVEL=0 -
gsl_CONFIG_INDEX_TYPE:
Version-1 default:std::ptrdiff_t
Version-0 default:gsl_CONFIG_SPAN_INDEX_TYPE(defaults tostd::size_t)
Reason: the GSL specifiesgsl_lite::indexto be a signed type. -
gsl_CONFIG_ALLOWS_SPAN_COMPARISON:
Version-1 default:gsl_CONFIG_ALLOWS_SPAN_COMPARISON=0
Version-0 default:gsl_CONFIG_ALLOWS_SPAN_COMPARISON=1
Reason: C++20std::span<>does not support comparison because semantics (deep vs. shallow) are unclear. -
gsl_CONFIG_NOT_NULL_EXPLICIT_CTOR:
Version-1 default:gsl_CONFIG_NOT_NULL_EXPLICIT_CTOR=1
Version-0 default:gsl_CONFIG_NOT_NULL_EXPLICIT_CTOR=0
Reason: see M-GSL/#395. (Note thatnot_null<>in Microsoft GSL has an implicit constructor, cf. M-GSL/#699.) -
gsl_CONFIG_TRANSPARENT_NOT_NULL:
Version-1 default:gsl_CONFIG_TRANSPARENT_NOT_NULL=1
Version-0 default:gsl_CONFIG_TRANSPARENT_NOT_NULL=0
Reason: enables conformant behavior fornot_null<>::get(). -
gsl_CONFIG_NARROW_THROWS_ON_TRUNCATION:
Version-1 default:gsl_CONFIG_NARROW_THROWS_ON_TRUNCATION=1
Version-0 default:gsl_CONFIG_NARROW_THROWS_ON_TRUNCATION=0
Reason: enables conformant behavior fornarrow<>()(cf. #52). -
default runtime contract violation handling:
Version-1 default:gsl_CONFIG_CONTRACT_VIOLATION_ASSERTS
Version-0 default:gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES
Reason: the mode enabled bygsl_CONFIG_CONTRACT_VIOLATION_ASSERTSis consistent with the behavior of theassert()macro while retaining runtime contract checks even ifNDEBUGis defined.
Deprecated features
The following features are deprecated since the indicated version. See macro gsl_CONFIG_DEPRECATE_TO_LEVEL on how to control deprecation using the indicated level.
| Version | Level | Feature / Notes |
|---|---|---|
| 1.0.0 | 9 | span<>::as_span<>() (unsafe) |
| 0.41.0 | 7 | basic_string_span<>, basic_zstring_span<> and related aliases(no longer part of the C++ Core Guidelines specification) |
| 0.35.0 | - | gsl_CONFIG_CONTRACT_LEVEL_ON, gsl_CONFIG_CONTRACT_LEVEL_OFF, gsl_CONFIG_CONTRACT_LEVEL_EXPECTS_ONLY and gsl_CONFIG_CONTRACT_LEVEL_ENSURES_ONLY(use gsl_CONFIG_CONTRACT_CHECKING_ON, gsl_CONFIG_CONTRACT_CHECKING_OFF, &nbs;gsl_CONFIG_CONTRACT_CHECKING_ENSURES_OFF, gsl_CONFIG_CONTRACT_CHECKING_EXPECTS_OFF instead) |
| 0.7.0 | - | gsl_CONFIG_ALLOWS_SPAN_CONTAINER_CTOR(use gsl_CONFIG_ALLOWS_UNCONSTRAINED_SPAN_CONTAINER_CTOR instead,or consider span(with_container, cont)) |
Removed features
The following features have been removed in the version indicated.
| Version | Feature / Notes |
|---|---|
| 1.0.0 | finally(), on_return(), and on_error() for pre-C++11 |
Owner() and implicit macros |
|
basic_string_span<>, basic_zstring_span<> and related aliases |
|
as_writeable_bytes(), call indexing for spans, and span::at() |
|
span( std::nullptr_t, index_type )( span( pointer, index_type ) is called instead) |
|
span( U *, index_type )( span( pointer, index_type ) is called instead) |
|
span( std::shared_ptr<T> const & p ) |
|
span( std::unique_ptr<T> const & p ) |
|
span<>::length()(use span<>::size() instead) |
|
span<>::length_bytes()(use span<>::size_bytes() instead) |
|
span<>::as_bytes(), span<>::as_writeable_bytes() |