Reference documentation

Contents

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(), and gsl_Assert() are compiled to runtime checks unless contract checking is disabled with the gsl_CONFIG_CONTRACT_CHECKING_OFF configuration macro.

  • The macro gsl_Verify() is compiled to a runtime check. gsl_Verify( c ) evaluates to the Boolean value of c. Additionally, if contract checking is enabled, gsl_Verify( c ) will enact a contract violation like gsl_Assert( c ) if c evaluates to false.

  • Contract checks expressed with gsl_ExpectsAudit(), gsl_EnsuresAudit(), and gsl_AssertAudit() are discarded by default. In order to have them checked at runtime, the gsl_CONFIG_CONTRACT_CHECKING_AUDIT configuration macro must be defined.

  • The macros gsl_ExpectsDebug(), gsl_EnsuresDebug(), and gsl_AssertDebug() are compiled to runtime checks unless contract checking is disabled by defining gsl_CONFIG_CONTRACT_CHECKING_OFF or assertions are disabled by defining NDEBUG. They can be used in place of the assert() macro from the C standard library. (Note that defining gsl_CONFIG_CONTRACT_CHECKING_AUDIT also enables checking of the gsl_*Debug() macros regardless of whether NDEBUG is defined.)

  • The gsl_Expects*(), gsl_Ensures*(), gsl_Assert*() categories of checks can be disabled individually with the gsl_CONFIG_CONTRACT_CHECKING_EXPECTS_OFF, gsl_CONFIG_CONTRACT_CHECKING_ENSURES_OFF, or gsl_CONFIG_CONTRACT_CHECKING_ASSERT_OFF configuration macros.

  • gsl_FailFast() is similar to gsl_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 this colorToString() implementation. gsl_FailFast() is employed here to ensure that passing unsupported values to colorToString() 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 in colorToString().

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 pointer P carries ownership of the object it points to, and is thus responsible for managing its lifetime.
  • not_null<P> and not_null_ic<P> indicate that a pointer P must not be nullptr.

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 to Q if P implicitly converts to Q.
    For example, not_null<int *> implicitly converts to int const *.
  • not_null<P> explicitly converts to Q if P explicitly converts to Q.
  • not_null<P> can be dereferenced if P can be dereferenced.
  • If P can be copied, not_null<P> can be copied. If P can 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, and not_null<std::shared_ptr<T>> is copyable and movable.
  • If P points to a struct, class, or union, not_null<P> defines the member access operator operator->.
  • If P has a member function P::get(), not_null<P> also defines a member function not_null<P>::get() which forwards to P::get().
  • If P is a function pointer or a nullable function object such as std::function<>, not_null<> defines the function call operator operator() which forwards the call to P:
    long call( not_null<long (*)(long)> func, long arg )
    {
        return func( arg );
    }
    
  • Q implicitly converts to not_null<P> if Q is non-nullable and implicitly converts to P.
    For example, std::labs implicitly converts to not_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!" );
    }
    
  • Q explicitly converts to not_null<P> if Q is nullable and implicitly converts to P, or if Q explicitly converts to P.
    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 P is hashable (that is, the std::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 )

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 invokes action upon destruction.
  • gsl_lite::on_return( action ) constructs and returns an object which invokes action upon destruction only if no exception was thrown.
  • gsl_lite::on_error( action ) constructs and returns an object which invokes action upon 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 type e.
    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 type e.
    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_type
gsl_lite::std11::false_type
gsl_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 with gsl_ExpectsAudit(), gsl_EnsuresAudit(), and gsl_AssertAudit() checked at runtime.

  • gsl_CONFIG_CONTRACT_CHECKING_ON (default)
    Define this macro to have contracts expressed with gsl_Expects(), gsl_Ensures(), gsl_Assert(), and gsl_FailFast() checked at runtime, Contracts expressed with gsl_ExpectsDebug(), gsl_EnsuresDebug(), and gsl_AssertDebug() are also checked at runtime (unless NDEBUG is defined and gsl_CONFIG_CONTRACT_CHECKING_AUDIT is not).
    This is the default.

  • NDEBUG This macro traditionally disables runtime checks for the assert() macro from the C standard library. Additionally, contracts expressed with gsl_ExpectsDebug(), gsl_EnsuresDebug(), and gsl_AssertDebug() are not evaluated or checked at runtime if NDEBUG is defined and gsl_CONFIG_CONTRACT_CHECKING_AUDIT is 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, and gsl_CONFIG_DEVICE_CONTRACT_CHECKING_OFF can 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_OFF in 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 with gsl_Expects(), gsl_ExpectsDebug(), and gsl_ExpectsAudit().

  • gsl_CONFIG_CONTRACT_CHECKING_ENSURES_OFF
    Define this macro to disable runtime checking of postcondition contracts expressed with gsl_Ensures(), gsl_EnsuresDebug(), and gsl_EnsuresAudit().

  • gsl_CONFIG_CONTRACT_CHECKING_ASSERT_OFF
    Define this macro to disable runtime checking of assertions expressed with gsl_Assert(), gsl_AssertDebug(), and gsl_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 macro gsl_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 call std::abort() if the assertion handler continues execution.

  • gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES
    Define this macro to call std::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 before std::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 a std::runtime_error-derived exception gsl_lite::fail_fast on 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 function gsl_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_lite
    

    Note that gsl_FailFast() will call std::terminate() if fail_fast_assert_handler() returns.

  • If desired, the macros gsl_CONFIG_DEVICE_CONTRACT_VIOLATION_ASSERTS, gsl_CONFIG_DEVICE_CONTRACT_VIOLATION_TRAPS, and gsl_CONFIG_DEVICE_CONTRACT_VIOLATION_CALLS_HANDLER can 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_TERMINATESgsl_CONFIG_DEVICE_CONTRACT_VIOLATION_ASSERTS
    • gsl_CONFIG_CONTRACT_VIOLATION_ASSERTSgsl_CONFIG_DEVICE_CONTRACT_VIOLATION_ASSERTS
    • gsl_CONFIG_CONTRACT_VIOLATION_THROWSgsl_CONFIG_DEVICE_CONTRACT_VIOLATION_ASSERTS
    • gsl_CONFIG_CONTRACT_VIOLATION_TRAPSgsl_CONFIG_DEVICE_CONTRACT_VIOLATION_TRAPS
    • gsl_CONFIG_CONTRACT_VIOLATION_CALLS_HANDLERgsl_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() and static_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 to 1. To suppress the verification, define gsl_CONFIG_VALIDATES_UNENFORCED_CONTRACT_EXPRESSIONS=0.

  • gsl_CONFIG_UNENFORCED_CONTRACTS_ASSUME
    For contracts expressed with gsl_Expects(), gsl_Ensures(), and gsl_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(), and gsl_AssertAudit() which are not checked at runtime (due to definition of NDEBUG or 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(), and gsl_Assert() should be used only for conditions that can be proven side-effect-free by the compiler, and gsl_ExpectsDebug(), gsl_EnsuresDebug(), gsl_AssertDebug(), gsl_ExpectsAudit(), gsl_EnsuresAudit(), and gsl_AssertAudit() for everything else. In practice, this means that gsl_Expects(), gsl_Ensures(), and gsl_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_ELIDE and gsl_CONFIG_DEVICE_UNENFORCED_CONTRACTS_ASSUME can 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:

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: byte has been superseded by std::byte in C++17.

  • gsl_CONFIG_DEPRECATE_TO_LEVEL:
    Version-1 default: gsl_CONFIG_DEPRECATE_TO_LEVEL=9 Version-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 to std::size_t)
    Reason: the GSL specifies gsl_lite::index to 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++20 std::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 that not_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 for not_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 for narrow<>() (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 by gsl_CONFIG_CONTRACT_VIOLATION_ASSERTS is consistent with the behavior of the assert() macro while retaining runtime contract checks even if NDEBUG is 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()