Reference documentation

Contents

Contract and assertion checks

(Core Guidelines reference: GSL.assert: Assertions)

There are several macros for expressing preconditions, postconditions, and invariants:

  • gsl_FailFast() to indicate unreachable code
  • gsl_Expects( cond ) for simple preconditions
  • gsl_Ensures( cond ) for simple postconditions
  • gsl_Assert( cond ) for simple assertions
  • gsl_ExpectsDebug( cond ) for debug-mode preconditions
  • gsl_EnsuresDebug( cond ) for debug-mode postconditions
  • gsl_AssertDebug( cond ) for debug-mode assertions
  • gsl_ExpectsAudit( cond ) for preconditions that are expensive or include potentially opaque function calls
  • gsl_EnsuresAudit( cond ) for postconditions that are expensive or include potentially opaque function calls
  • gsl_AssertAudit( cond ) for assertions that are expensive or include potentially opaque function calls

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 ] );
}

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.

  • 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().

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

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 );
    }
    
  • 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.

For C++14 and older, where class template argument deduction is not available, gsl-lite defines a set of helper functions make_not_null() for explicitly constructing not_null<> objects.

gsl-lite additionally 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. It therefore does not define a subscript operator, pointer increment or decrement operators, or pointer addition or subtraction operators, and gsl_lite::make_unique<T>() and gsl_lite::make_shared<T>() are not defined for array types.

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();
}

Nullability and the moved-from state

Because not_null<P> retains the copyability and movability of P, a not_null<P> object may have a moved-from state if the underlying pointer P has one. Therefore, a not_null<P> object may in fact 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.

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 also defines a set of helper functions make_not_null() for explicitly constructing not_null<> objects. This is useful for type inference in C++14 and older where class template argument deduction is not available. Example:

auto filePtr = FilePtr( std::fopen( ... ) );
if ( filePtr == nullptr ) throw std::runtime_error( ... );
auto file = FileHandle( make_not_null( std::move( filePtr ) ) );

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 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 and cwzstring are gsl-lite extensions and not part of the C++ Core Guidelines.)

gsl-lite defines the aliases gsl_lite::zstring, gsl_lite::czstring, gsl_lite::wzstring, and gsl_lite::cwzstring for 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 *

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 version number when compiled with GNU GCC, 0 otherwise
gsl_COMPILER_CLANG_VERSION Evaluates to version number when compiled with Clang, 0 otherwise
gsl_COMPILER_MSVC_VERSION Evaluates to version number when compiled with Microsoft Visual C++, 0 otherwise
gsl_COMPILER_APPLECLANG_VERSION Evaluates to version number when compiled with Apple Clang, 0 otherwise
gsl_COMPILER_NVCC_VERSION Evaluates to version number when compiled with NVIDIA NVCC, 0 otherwise
gsl_COMPILER_ARMCC_VERSION Evaluates to 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 )
    
  • 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 )
    

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)

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, and if the assert() macro is available for runtime checks (that is, if NDEBUG is not defined), contract checking macros are implemented in terms of assert(). If assert() is unavailable (i.e. if NDEBUG was defined), std::abort() is called directly when a contract is violated.
    This is the default.

    This option may be preferable over gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES because assert() prints diagnostic information (such as the current source file, a line number, and the function name), and because vendor-specific extensions of assert() can be used (for instance, the assert() implementation of the Microsoft C runtime displays a dialog box which permits breaking into the debugger or continuing execution).

    Note that gsl_FailFast() will call std::abort() if assert() continues execution.

  • gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES
    Define this macro to call std::terminate() on a contract violation.

  • 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 for writing unit tests that exercise 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_TERMINATES → gsl_CONFIG_DEVICE_CONTRACT_VIOLATION_ASSERTS
    • gsl_CONFIG_CONTRACT_VIOLATION_ASSERTS → gsl_CONFIG_DEVICE_CONTRACT_VIOLATION_ASSERTS
    • gsl_CONFIG_CONTRACT_VIOLATION_THROWS → gsl_CONFIG_DEVICE_CONTRACT_VIOLATION_ASSERTS
    • gsl_CONFIG_CONTRACT_VIOLATION_TRAPS → gsl_CONFIG_DEVICE_CONTRACT_VIOLATION_TRAPS
    • gsl_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() 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_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.

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 are removed since the indicated version.

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()