Reference documentation
Contents
- Contract and assertion checks:
gsl_Expects( pred )
,gsl_Ensures( pred )
,gsl_Assert( pred )
, and more - Pointer annotations:
owner<P>
,not_null<P>
, andnot_null_ic<P>
- Numeric type conversions:
narrow<T>( u )
,narrow_failfast<T>( u )
, andnarrow_cast<T>( u )
- Safe contiguous ranges:
span<T, Extent>
- Bounds-checked element access:
at( container, index )
- Integer type aliases:
index
,dim
,stride
,diff
- String type aliases:
zstring
,czstring
,wzstring
,cwzstring
- Ad hoc resource management (C++11 and higher):
finally( action )
,on_return( action )
, andon_error( action )
- Feature checking macros
- Polyfills
- Configuration options and switches
- Configuration changes, deprecated and removed features
Contract and assertion checks
(Core Guidelines reference: GSL.assert: Assertions)
There are several macros for expressing preconditions, postconditions, and invariants:
gsl_FailFast()
to indicate unreachable codegsl_Expects( cond )
for simple preconditionsgsl_Ensures( cond )
for simple postconditionsgsl_Assert( cond )
for simple assertionsgsl_ExpectsDebug( cond )
for debug-mode preconditionsgsl_EnsuresDebug( cond )
for debug-mode postconditionsgsl_AssertDebug( cond )
for debug-mode assertionsgsl_ExpectsAudit( cond )
for preconditions that are expensive or include potentially opaque function callsgsl_EnsuresAudit( cond )
for postconditions that are expensive or include potentially opaque function callsgsl_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()
, andgsl_Assert()
are compiled to runtime checks unless contract checking is disabled with thegsl_CONFIG_CONTRACT_CHECKING_OFF
configuration macro. -
Contract checks expressed with
gsl_ExpectsAudit()
,gsl_EnsuresAudit()
, andgsl_AssertAudit()
are discarded by default. In order to have them checked at runtime, thegsl_CONFIG_CONTRACT_CHECKING_AUDIT
configuration macro must be defined. -
The macros
gsl_ExpectsDebug()
,gsl_EnsuresDebug()
, andgsl_AssertDebug()
are compiled to runtime checks unless contract checking is disabled by defininggsl_CONFIG_CONTRACT_CHECKING_OFF
or assertions are disabled by definingNDEBUG
. They can be used in place of theassert()
macro from the C standard library. (Note that defininggsl_CONFIG_CONTRACT_CHECKING_AUDIT
also enables checking of thegsl_*Debug()
macros regardless of whetherNDEBUG
is defined.) -
The
gsl_Expects*()
,gsl_Ensures*()
,gsl_Assert*()
categories of checks can be disabled individually with thegsl_CONFIG_CONTRACT_CHECKING_EXPECTS_OFF
,gsl_CONFIG_CONTRACT_CHECKING_ENSURES_OFF
, orgsl_CONFIG_CONTRACT_CHECKING_ASSERT_OFF
configuration macros. -
gsl_FailFast()
is similar togsl_Assert( false )
but is guaranteed to interrupt the current path of execution even if contract checking is disabled with configuration macros. It is useful in places which should not be reachable during program execution. For example:enum class Color : int { red, green, blue }; std::string colorToString( Color color ) { switch (color) { case Color::red: return "red"; case Color::green: return "green"; case Color::blue: return "blue"; } gsl_FailFast(); }
The C++ language permits casting any representable integer value to an enum. Therefore,
colorToString(Color(0xFF00FF))
is legal C++, but not actually supported by thiscolorToString()
implementation.gsl_FailFast()
is employed here to ensure that passing unsupported values tocolorToString()
will be detected at runtime.gsl_FailFast()
behaves as if annotated by the[[noreturn]]
attribute. A C++11 compiler will therefore not emit a warning about a missing return statement incolorToString()
.
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 pointerP
carries ownership of the object it points to, and is thus responsible for managing its lifetime.not_null<P>
andnot_null_ic<P>
indicate that a pointerP
must not benullptr
.
owner<P>
(C++11 and higher)
gsl_lite::owner<P>
is a type alias that solely serves the purpose of expressing intent:
template< class P >
requires ( std::is_pointer_v<P> )
using owner = P;
(Note: The actual definition uses a SFINAE constraint to support C++11 to C++17).
As far as the type system and the runtime behavior is concerned, owner<P>
is exactly equivalent to P
. However, the annotation conveys
intent to a reader, and static analysis tools may use the annotation to impose semantic checks based on the assumption of ownership.
If possible, a smart pointer with automatic lifetime management such as std::unique_ptr<>
or std::shared_ptr<>
should be used instead. The purpose of owner<>
is annotation of
raw pointers which cannot currently be replaced with a smart pointer.
not_null<P>
gsl_lite::not_null<P>
is a class template that wraps a pointer type P
while enforcing non-nullability.
not_null<P>
has no default constructor, and its unary constructors use gsl_Expects()
to check
that their argument is not nullptr
. Therefore, when attempting to construct a not_null<P>
from a null pointer, a runtime contract violation is triggered:
using gsl_lite::not_null;
int i = 42;
int * pi = &i;
int * pn = nullptr;
// compile error: no default constructor
//not_null<int *> n;
// compile error: no implicit conversion
//not_null<int *> npi = pi;
// explicit conversion: runtime null check
not_null<int *> npi = not_null( pi );
// compile error: no implicit conversion
//not_null<int *> nn = nullptr;
// compile error: no explicit conversion
//not_null<int *> nn = not_null( nullptr );
// explicit conversion: runtime null check ⇒ runtime contract violation
not_null<int *> npn = not_null( pn );
Motivation
When defining function signatures, it is customary to use pointers and references to indicate whether an object reference is required or optional:
void lock( Mutex & m ); // requires a valid object reference
struct ListNode
{
ListNode* prev;
ListNode* next;
int payload;
};
bool tryRemove( ListNode * x ); // also accepts a `nullptr`
But this convention does not apply to every situation. For example, accepting a pointer argument can also emphasize the fact that the object’s memory address may be taken and stored, as in the case of a list or tree node. Storing the memory address of a by-reference argument “feels” wrong, and may be flagged by static analyzers:
void insertAfter( ListNode & x, ListNode & newNode )
{
newNode.prev = &x; // <-- Eww.
newNode.next = x.next;
x.next = &newNode; // <-- Eww.
}
The function would be less awkward if it accepted pointers.
To make sure a pointer argument is not nullptr
, we can add a precondition check to the function:
void insertAfter( ListNode * x, ListNode * newNode )
{
gsl_Expects( x != nullptr );
gsl_Expects( newNode != nullptr );
newNode->prev = x;
newNode->next = x->next;
x->next = newNode;
}
Writing all the precondition checks against nullptr
quickly becomes tedious. And unlike the contract checks once envisioned
for C++20, precondition checks expressed with gsl_Expects()
are not part of the function signature, which therefore does
not convey that it cannot handle nullptr
input.
This is where not_null<>
comes in. With not_null<>
, the precondition can be “lifted” into the type system,
and thus into the function signature:
void insertAfter( not_null<ListNode *> x, not_null<ListNode *> newNode )
{
newNode->prev = x;
newNode->next = x->next;
x->next = newNode;
}
All not_null<>
constructors check their arguments for nullptr
with gsl_Expects()
, so the functions above can already assume
that their arguments will never be nullptr
, and the explicit precondition checks can be omitted.
When calling the function insertAfter()
, it is understood that the caller passes ownership of the node newNode
.
Transfer of ownership is best expressed with a smart pointer such as std::unique_ptr<>
, which can also be used as an
argument to not_null<>
:
void insertAfter( not_null<ListNode *> x, not_null<std::unique_ptr<ListNode>> newNode );
A not_null<std::unique_ptr<>>
can be passed around just like a std::unique_ptr<>
:
void insertAfterEnd( not_null<ListNode *> lastNode, not_null<std::unique_ptr<ListNode>> newNode )
{
gsl_Expects( lastNode->next == nullptr );
insertAfter( lastNode, std::move( newNode ) );
}
Because newNode
is non-nullable, releasing the pointer it holds is not straightforward:
void insertAfter( not_null<ListNode *> x, not_null<std::unique_ptr<ListNode>> newNode )
{
newNode->prev = x;
newNode->next = x->next;
x->next = newNode.release(); // error: `not_null<>` has no member function `release()`
}
To extract the raw pointer, we first have to extract a nullable unique_ptr<>
:
std::unique_ptr<ListNode> rawNewNode = std::move( newNode );
x->next = rawNewNode.release();
This can be written as a one-liner with gsl_lite::as_nullable()
:
x->next = gsl_lite::as_nullable( std::move( newNode ) ).release();
Reference
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 toQ
ifP
implicitly converts toQ
.
For example,not_null<int *>
implicitly converts toint const *
.not_null<P>
explicitly converts toQ
ifP
explicitly converts toQ
.not_null<P>
can be dereferenced ifP
can be dereferenced.- If
P
can be copied,not_null<P>
can be copied. IfP
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, andnot_null<std::shared_ptr<T>>
is copyable and movable. - If
P
points to astruct
,class
, orunion
,not_null<P>
defines the member access operatoroperator->
. - If
P
has a member functionP::get()
,not_null<P>
also defines a member functionnot_null<P>::get()
which forwards toP::get()
. - If
P
is a function pointer or a nullable function object such asstd::function<>
,not_null<>
defines the function call operatoroperator()
which forwards the call toP
:long call( not_null<long (*)(long)> func, long arg ) { return func( arg ); }
Q
implicitly converts tonot_null<P>
ifQ
is non-nullable and implicitly converts toP
.
For example,std::labs
implicitly converts tonot_null<long (*)(long)>
:long callAbs( long val ) { return call( std::labs, val ); }
Q
explicitly converts tonot_null<P>
ifQ
is nullable and implicitly converts toP
, or ifQ
explicitly converts toP
.
For example:std::string readLine( not_null<std::FILE*> file ); std::FILE* file = ...; //readLine( file ); // does not compile: `file` is nullable readLine( not_null( file ) ); // compiles and executes a check at runtime
- If
P
is hashable (that is, thestd::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 )
, a checked numeric castnarrow_failfast<T>( u )
, a checked numeric castnarrow_cast<T>( u )
, an unchecked numeric cast
narrow<T>( u )
gsl_lite::narrow<T>( u )
is a numeric cast that is not meant to be lossy. If narrowing leads to a change of sign or
loss of information, an exception of type gsl_lite::narrowing_error
is thrown.
Example:
double volume = ...; // (mÂł)
double bucketCapacity = ...; // (mÂł)
double numBucketsF = std::ceil( volume/bucketCapacity );
try
{
auto numBuckets = gsl_lite::narrow<int>( numBucketsF );
std::cout << "Number of buckets required: " << numBuckets;
fillBuckets( numBuckets );
}
catch ( gsl_lite::narrowing_error const & )
{
std::cerr << "This is more than I can handle.\n";
}
In this example, an exception will be thrown if numBucketsF
is not an integer (for instance, std::ceil(-INFINITY)
will return -INFINITY
),
or if the value cannot be represented by int
.
narrow_failfast<T>( u )
(Note: narrow_failfast<T>( u )
is a gsl-lite extension and not part of the C++ Core Guidelines.)
gsl_lite::narrow_failfast<T>( u )
is a numeric cast that is not meant to be lossy, which is verified with
gsl_Assert()
. If narrowing leads to a change of sign or loss of information, an
assertion violation is triggered.
The narrow<T>( u )
function specified by the C++ Core Guidelines throws an exception, thereby indicating exceptional circumstances
(for instance, “input data too large”). The exception may be caught and dealt with at runtime. Contrariwise, the purpose of
narrow_failfast<T>( u )
is to detect programming errors which the user of the program cannot do anything about.
Example 1:
void printCmdArgs( gsl_lite::span<gsl_lite::zstring const> cmdArgs );
int main( int argc, char * argv[] )
{
auto args = gsl_lite::span(
argv,
// Something is seriously wrong if this cast fails.
gsl_lite::narrow_failfast<std::size_t>( argc ) );
printCmdArgs( args );
}
Example 2:
auto vec = std::vector{ 1, 2, 3 };
int elem = 2;
auto pos = std::find( vec.begin(), vec.end(), elem );
// Bug: we accidentally swapped `pos` and `vec.end()`.
auto delta = pos - vec.end();
// Assertion violation: `delta` is negative.
auto subrangeSize = gsl_lite::narrow_failfast<std::size_t>( delta );
auto subrange = std::vector<int>( subrangeSize );
narrow_cast<T>( u )
narrow_cast<T>( u )
is a numeric cast in which loss of information is acceptable. It is exactly equivalent to static_cast<T>( u )
,
the only difference being that narrow_cast<>()
conveys the intent that truncation or sign change is acceptable or even desired.
Example 1:
// Sign change to 0xFFFFFFFF
auto allBitsSet = gsl_lite::narrow_cast<std::uint32_t>( -1 );
Example 2:
int floor( float val )
{
gsl_Expects(
val >= std::numeric_limits<int>::lowest() &&
val <= std::numeric_limits<int>::max() );
return gsl_lite::narrow_cast<int>( val ); // truncation is desired here
}
Safe contiguous ranges
(Core Guidelines reference: GSL.view: Views)
gsl-lite defines a class gsl_lite::span<T, Extent>
that represents a contiguous sequence of objects. The interface
of span<>
is identical to that of std::span<>
,
but all operations in gsl-lite’s span<>
use gsl_Expects()
to
check their preconditions at runtime. span<>::iterator
also verifies the preconditions of all its operations
with gsl_ExpectsDebug()
.
gsl-lite also defines a set of helper functions make_span()
for explicitly constructing span<>
objects. This is useful
for type inference in C++14 and older where class template argument deduction
is not available. Example:
void printCmdArgs( gsl_lite::span<gsl_lite::zstring const> cmdArgs );
int main( int argc, char * argv[] )
{
auto args = gsl_lite::make_span(
argv, gsl_lite::narrow_failfast<std::size_t>( argc ) );
printCmdArgs( args );
}
Bounds-checked element access
(Core Guidelines reference: GSL.util: Utilities)
The function gsl_lite::at( container, index )
offers bounds-checked element access for all sized containers with random access.
Exposition-only definition:
template< class Container >
auto at( Container& c, index i )
{
gsl_Expects( i >= 0 && i < std::ssize( c ) );
return c[ i ];
}
Integer type aliases
(Core Guidelines reference: GSL.util: Utilities)
(Note: dim
, stride
, and diff
are gsl-lite extensions and not part of the C++ Core Guidelines.)
Rule ES.107 of the C++ Core Guidelines suggests,
“Don’t use unsigned
for subscripts, prefer gsl::index
,” giving several good reasons for preferring a signed over an unsigned
type for indexing. For this purpose, the GSL defines index
as a type alias for std::ptrdiff_t
.
gsl-lite defines the type alias gsl_lite::index
along with the aliases gsl_lite::dim
, gsl_lite::stride
, and gsl_lite::diff
:
Type alias | Purpose |
---|---|
index |
Signed integer type for indexes and subscripts |
dim |
Signed integer type for sizes |
stride |
Signed integer type for index strides |
diff |
Signed integer type for index differences |
Example:
auto x = std::vector<double>{ ... };
auto dx = std::vector<double>( x.size() - 1 );
gsl_lite::dim n = std::ssize( x );
for ( gsl_lite::index i = 0; i < n - 1; ++i )
{
dx[ i ] = x[ i + 1 ] - x[ i ];
}
index
, dim
, stride
, and diff
are all aliases for std::ptrdiff_t
unless the gsl_CONFIG_INDEX_TYPE
configuration macro is set to a different type (which is not recommended).
String type aliases
(Core Guidelines reference: GSL.view: Views)
(Note: wzstring
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 invokesaction
upon destruction.gsl_lite::on_return( action )
constructs and returns an object which invokesaction
upon destruction only if no exception was thrown.gsl_lite::on_error( action )
constructs and returns an object which invokesaction
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 typee
.
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 typee
.
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 withgsl_ExpectsAudit()
,gsl_EnsuresAudit()
, andgsl_AssertAudit()
checked at runtime. -
gsl_CONFIG_CONTRACT_CHECKING_ON
(default)
Define this macro to have contracts expressed withgsl_Expects()
,gsl_Ensures()
,gsl_Assert()
, andgsl_FailFast()
checked at runtime, Contracts expressed withgsl_ExpectsDebug()
,gsl_EnsuresDebug()
, andgsl_AssertDebug()
are also checked at runtime (unlessNDEBUG
is defined andgsl_CONFIG_CONTRACT_CHECKING_AUDIT
is not).
This is the default. -
NDEBUG
This macro traditionally disables runtime checks for theassert()
macro from the C standard library. Additionally, contracts expressed withgsl_ExpectsDebug()
,gsl_EnsuresDebug()
, andgsl_AssertDebug()
are not evaluated or checked at runtime ifNDEBUG
is defined andgsl_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
, andgsl_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 withgsl_Expects()
,gsl_ExpectsDebug()
, andgsl_ExpectsAudit()
. -
gsl_CONFIG_CONTRACT_CHECKING_ENSURES_OFF
Define this macro to disable runtime checking of postcondition contracts expressed withgsl_Ensures()
,gsl_EnsuresDebug()
, andgsl_EnsuresAudit()
. -
gsl_CONFIG_CONTRACT_CHECKING_ASSERT_OFF
Define this macro to disable runtime checking of assertions expressed withgsl_Assert()
,gsl_AssertDebug()
, andgsl_AssertAudit()
.
Contract violation handling
The following macros control the handling of runtime contract violations:
-
gsl_CONFIG_CONTRACT_VIOLATION_ASSERTS
(default)
If this macro is defined, and if theassert()
macro is available for runtime checks (that is, ifNDEBUG
is not defined), contract checking macros are implemented in terms ofassert()
. Ifassert()
is unavailable (i.e. ifNDEBUG
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
becauseassert()
prints diagnostic information (such as the current source file, a line number, and the function name), and because vendor-specific extensions ofassert()
can be used (for instance, theassert()
implementation of the Microsoft C runtime displays a dialog box which permits breaking into the debugger or continuing execution).Note that
gsl_FailFast()
will callstd::abort()
ifassert()
continues execution. -
gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES
Define this macro to callstd::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 astd::runtime_error
-derived exceptiongsl_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 functiongsl_lite::fail_fast_assert_handler()
on a contract violation. The user must provide a definition of the following function:namespace gsl_lite { gsl_api void fail_fast_assert_handler( char const * expression, char const * message, char const * file, int line ); } // namespace gsl_lite
Note that
gsl_FailFast()
will callstd::terminate()
iffail_fast_assert_handler()
returns.- If desired, the macros
gsl_CONFIG_DEVICE_CONTRACT_VIOLATION_ASSERTS
,gsl_CONFIG_DEVICE_CONTRACT_VIOLATION_TRAPS
, andgsl_CONFIG_DEVICE_CONTRACT_VIOLATION_CALLS_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()
andstatic_assert()
. This may lead to problems if the contract check expression cannot be used in an unevaluated context, for instance, when using a lambda expression in C++11/14/17.The compile-time verification of contract check expressions is controlled by the configuration macro
gsl_CONFIG_VALIDATES_UNENFORCED_CONTRACT_EXPRESSIONS
, which defaults to1
. To suppress the verification, definegsl_CONFIG_VALIDATES_UNENFORCED_CONTRACT_EXPRESSIONS=0
. -
gsl_CONFIG_UNENFORCED_CONTRACTS_ASSUME
For contracts expressed withgsl_Expects()
,gsl_Ensures()
, andgsl_Assert()
which are not checked as a result of configuration, instruct the compiler to assume that they always hold true. This is expressed with compiler-specific intrinsics such as__assume()
.Contract checks expressed with
gsl_ExpectsDebug()
,gsl_EnsuresDebug()
,gsl_AssertDebug()
,gsl_ExpectsAudit()
,gsl_EnsuresAudit()
, andgsl_AssertAudit()
which are not checked at runtime (due to definition ofNDEBUG
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()
, andgsl_Assert()
should be used only for conditions that can be proven side-effect-free by the compiler, andgsl_ExpectsDebug()
,gsl_EnsuresDebug()
,gsl_AssertDebug()
,gsl_ExpectsAudit()
,gsl_EnsuresAudit()
, andgsl_AssertAudit()
for everything else. In practice, this means thatgsl_Expects()
,gsl_Ensures()
, andgsl_Assert()
should only be used for simple comparisons of scalar values, for simple inlineable getters, and for comparisons of class objects with trivially inlineable comparison operators.Revisiting the example given above:
template< class RandomIt > auto median( RandomIt first, RandomIt last ) { // Comparing iterators for equality boils down to a comparison of pointers. An optimizing // compiler will inline the comparison operator and understand that the comparison is free of // side-effects, and hence generate no code in `gsl_CONFIG_UNENFORCED_CONTRACTS_ASSUME` mode. gsl_Expects( first != last ); // If we cannot trust the compiler to understand that this function call is free of // side-effects, we should use `gsl_ExpectsDebug()` or `gsl_ExpectsAudit()`. This particular // function call is expensive, so we use an audit-level contract check. gsl_ExpectsAudit( std::is_sorted( first, last ) ); auto count = last - first; return count % 2 != 0 ? first[ count / 2 ] : std::midpoint( first[ count / 2 ], first[ count / 2 + 1 ] ); }
-
If desired, the macros
gsl_CONFIG_DEVICE_UNENFORCED_CONTRACTS_ELIDE
andgsl_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 bystd::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 tostd::size_t
)
Reason: the GSL specifiesgsl_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++20std::span<>
does not support comparison because semantics (deep vs. shallow) are unclear. -
gsl_CONFIG_NOT_NULL_EXPLICIT_CTOR
:
Version-1 default:gsl_CONFIG_NOT_NULL_EXPLICIT_CTOR=1
Version-0 default:gsl_CONFIG_NOT_NULL_EXPLICIT_CTOR=0
Reason: see M-GSL/#395. (Note thatnot_null<>
in Microsoft GSL has an implicit constructor, cf. M-GSL/#699.) -
gsl_CONFIG_TRANSPARENT_NOT_NULL
:
Version-1 default:gsl_CONFIG_TRANSPARENT_NOT_NULL=1
Version-0 default:gsl_CONFIG_TRANSPARENT_NOT_NULL=0
Reason: enables conformant behavior fornot_null<>::get()
. -
gsl_CONFIG_NARROW_THROWS_ON_TRUNCATION
:
Version-1 default:gsl_CONFIG_NARROW_THROWS_ON_TRUNCATION=1
Version-0 default:gsl_CONFIG_NARROW_THROWS_ON_TRUNCATION=0
Reason: enables conformant behavior fornarrow<>()
(cf. #52). -
default runtime contract violation handling:
Version-1 default:gsl_CONFIG_CONTRACT_VIOLATION_ASSERTS
Version-0 default:gsl_CONFIG_CONTRACT_VIOLATION_TERMINATES
Reason: the mode enabled bygsl_CONFIG_CONTRACT_VIOLATION_ASSERTS
is consistent with the behavior of theassert()
macro while retaining runtime contract checks even ifNDEBUG
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() |