GSL
GSL: Guidelines support library
-
The GSL is a small library of facilities designed to support this set of guidelines.
-
Without these facilities, the guidelines would have to be far more restrictive on language details.
-
The Core Guidelines support library is defined in
namespace gsland the names might be aliases for standard library or other well-known library names. -
Using the (compile-time) indirection through the
gsl namespaceallows for experimentation and for local variants of the support facilities. -
The GSL is header only, and can be found at GSL: Guidelines support library.
-
The support library facilities are designed to be extremely lightweight (zero-overhead) so that they impose no overhead compared to using conventional alternatives. Where desirable, they can be "instrumented" with additional functionality (e.g., checks) for tasks such as debugging.
-
These Guidelines use types from the standard (e.g., C++17) in addition to ones from the GSL. For example, we assume a variant type, but this is not currently in GSL. Eventually, use the one voted into C++17.
-
Some of the GSL types listed below might not be supported in the library you use due to technical reasons such as limitations in the current versions of C++. Therefore, please consult your GSL documentation to find out more.
-
We plan for a "ISO C++ standard style" semi-formal specification of the GSL.
-
We rely on the ISO C++ Standard Library and hope for parts of the GSL to be absorbed into the standard library.
FAQ.54: Has the GSL (guidelines support library) been approved by the ISO C++ standards committee?
- No. The GSL exists only to supply a few types and aliases that are not currently in the standard library.
- If the committee decides on standardized versions (of these or other types that fill the same need) then they can be removed from the GSL.
GSL.view: Views
-
These types allow the user to distinguish between owning and non-owning pointers and between pointers to a single object and pointers to the first element of a sequence.
-
These "views" are never owners.
-
References are never owners (see R.4).
-
Note: References have many opportunities to outlive the objects they refer to (returning a local variable by reference, holding a reference to an element of a vector and doing
push_back, binding tostd::max(x, y + 1), etc). -
The Lifetime safety profile aims to address those things, but even so
owner<T&>does not make sense and is discouraged. -
The names are mostly ISO standard-library style (lower case and underscore):
T* // The T* is not an owner, might be null; assumed to be pointing to a single element.
T& // The T& is not an owner and can never be a "null reference"; references are always bound to objects.
owner<T*> // a T* that owns the object pointed/referred to; might be nullptr.
- The "raw-pointer" notation (e.g.
int*) is assumed to have its most common meaning; that is, a pointer points to an object, but does not own it. - Owners should be converted to resource handles (e.g.,
unique_ptrorvector<T>) or markedowner<T*>. - owner is used to mark owning pointers in code that cannot be upgraded to use proper resource handles.
- Reasons for that include:
- Cost of conversion.
- The pointer is used with an ABI.
- The pointer is part of the implementation of a resource handle.
- An
owner<T> differs from a resource handle for aTby still requiring an explicit delete.
- Reasons for that include:
- An
owner<T>is assumed to refer to an object on the free store (heap). - If something is not supposed to be
nullptr, say so:
not_null<T> // T is usually a pointer type (e.g., not_null<int*> and not_null<owner<Foo*>>) that must not be nullptr.
// T can be any type for which ==nullptr is meaningful.
span<T> // [p:p+n), constructor from {p, q} and {p, n}; T is the pointer type
span_p<T> // {p, predicate} [p:q) where q is the first element for which predicate(*p) is true
- A
span<T>refers to zero or more mutableTs unlessTis aconsttype. - "Pointer arithmetic" is best done within spans.
- A
char*that points to more than one char but is not a C-style string (e.g., a pointer into an input buffer) should be represented by aspan.
zstring // a char* supposed to be a C-style string; that is, a zero-terminated sequence of char or nullptr
czstring // a const char* supposed to be a C-style string; that is, a zero-terminated sequence of const char or nullptr
- Logically, those last two aliases are not needed, but we are not always logical, and they make the distinction between a pointer to one char and a pointer to a C-style string explicit.
- A sequence of characters that is not assumed to be zero-terminated should be a
span<char>, or if that is impossible because of ABI issues achar*, rather than azstring. - Use
not_null<zstring>for C-style strings that cannot be nullptr.
FAQ.55: If you're using the standard types where available, why is the GSL span<char> different from the string_view ...
- The consensus on the taxonomy of views for the C++ Standard Library was that "view" means "read-only", and "span" means "read/write".
- If you only need a read-only view of characters that does not need guaranteed bounds-checking and you have C++17, use C++17
std::string_view. - Otherwise, if you need a read-write view that does not need guaranteed bounds-checking and you have C++20, use C++20
std::span<char>. - Otherwise, use
gsl::span<char>.
- If you only need a read-only view of characters that does not need guaranteed bounds-checking and you have C++17, use C++17
GSL.owner: Ownership pointers
unique_ptr<T> // unique ownership: std::unique_ptr<T>
shared_ptr<T> // shared ownership: std::shared_ptr<T> (a counted pointer)
stack_array<T> // A stack-allocated array. The number of elements are determined at construction and fixed thereafter. The elements are mutable unless T is a const type.
dyn_array<T> // ??? needed ??? A heap-allocated array. The number of elements are determined at construction and fixed thereafter. The elements are mutable unless T is a const type. Basically a span that allocates and owns its elements.
FAQ.56: Is owner the same as the proposed observer_ptr?
- No. owner owns, is an alias, and can be applied to any indirection type.
- The main intent of
observer_ptris to signify a non-owning pointer.
FAQ.57: Is stack_array the same as the standard array?
- No.
stack_arrayis guaranteed to be allocated on the stack. - Although a
std::arraycontains its storage directly inside itself, thearrayobject can be put anywhere, including the heap.
FAQ.58: Is dyn_array the same as vector or the proposed dynarray?
- No.
dyn_arrayis not resizable, and is a safe way to refer to a heap-allocated fixed-size array. - Unlike
vector, it is intended to replacearray-new[]. - Unlike the
dynarraythat has been proposed in the committee, this does not anticipate compiler/language magic to somehow allocate it on the stack- when it is a member of an object that is allocated on the stack; it simply refers to a "dynamic" or heap-based array.
GSL.assert: Assertions
Expects // pre-condition assertion. Currently placed in function bodies. Later, should be moved to declarations.
// Expects(p) terminates the program unless p == true
// Expects is under control of some options (enforcement, error message, alternatives to terminate)
Ensures // post-condition assertion. Currently placed in function bodies. Later, should be moved to declarations.
- These assertions are currently macros (yuck!) and must appear in function definitions (only) pending standard committee decisions on contracts and assertion syntax.
- See the contract proposal; using the attribute syntax, for example,
Expects(p)will become[[expects: p]].
FAQ.59: Is Expects the same as assert?
- No. It is a placeholder for language support for contract preconditions.
FAQ.60: Is Ensures the same as assert?
- No. It is a placeholder for language support for contract postconditions.
GSL.util: Utilities
finally // finally(f) makes a final_action{f} with a destructor that invokes f
narrow_cast // narrow_cast<T>(x) is static_cast<T>(x)
narrow // narrow<T>(x) is static_cast<T>(x) if static_cast<T>(x) == x with no signedness promotions
// or it throws narrowing_error (e.g., narrow<unsigned>(-42) throws)
[[implicit]] // "Marker" to put on single-argument constructors to explicitly make them non-explicit.
move_owner // p = move_owner(q) means p = q but ???
joining_thread // a RAII style version of std::thread that joins.
index // a type to use for all container and array indexing (currently an alias for ptrdiff_t)
GSL.concept: Concepts
- These concepts (type predicates) are borrowed from Andrew Sutton's Origin library, the Range proposal, and the ISO WG21 Palo Alto TR. Many of them are very similar to what became part of the ISO C++ standard in C++20.
String
Number
Boolean
Range // in C++20, std::ranges::range
Sortable // in C++20, std::sortable
EqualityComparable // in C++20, std::equality_comparable
Convertible // in C++20, std::convertible_to
Common // in C++20, std::common_with
Integral // in C++20, std::integral
SignedIntegral // in C++20, std::signed_integral
SemiRegular // in C++20, std::semiregular
Regular // in C++20, std::regular
TotallyOrdered // in C++20, std::totally_ordered
Function // in C++20, std::invocable
RegularFunction // in C++20, std::regular_invocable
Predicate // in C++20, std::predicate
Relation // in C++20, std::relation
GSL.ptr: Smart pointer concepts
Pointer // A type with *, ->, ==, and default construction (default construction is assumed to set the singular "null" value)
Unique_pointer // A type that matches Pointer, is movable, and is not copyable
Shared_pointer // A type that matches Pointer, and is copyable