-
Notifications
You must be signed in to change notification settings - Fork 0
Home
A recreation of much of the modern C++ STL in C++98; requiring no language extensions, compiler features, or C++0x and up support. Obviously this cannot replicate core language features such as move semantics; however a surprising amount of the modern standard library is perfectly implementable even as far back as C++98.
The interface has been designed to make switching up to a modern C++ standard as seamless as possible. While I would recommend exercising due care and diligence with your code, in most situations it can be achieved simply by replacing #include directives and finding-and-replacing dp:: with std::, such that a code block which looks like this today
#include "cpp98/optional.h"
dp::optional<Data_Type> get_data(){
if(data_can_be_obtained) return obtain_data();
else return dp::nullopt;
}Can be simply upgraded upon reaching C++17 to
#include <optional>
std::optional<Data_Type> get_data(){
if(data_can_be_obtained) return obtain_data();
else return std::nullopt;
}Note I make no promise of ABI compatibility between my tools here and their standard equivalents.
This is a header-only library. All the code you need is included in the cpp98 subdirectory of the include directory. The bits subdirectory is for files which are used in the internal workings of the library. They are not intended to be a part of the public interface, and I do not recommend using them in your projects.
Separate from the main part of this project is a header which contains string_view style constructs for Embarcadero AnsiString and UnicodeString types; as well as overloads of the range functions for these. As this is non-standard C++, it is found in the borland subdirectory, in borland/borland_strings.h. It will be disabled if you are not using a Borland compiler and can be safely ignored in that case.
The headers included in this library are:
-
algorithm - Standard library algorithms added in C++11 and onwards, which would normally live in the
<algorithm>header. -
array - An analogue of
std::array, sharing the same functionality and interface. -
bit - A recreation of the bitwise functions in
<bit>. -
expected - An analogue of
std::expected- A type which contains either an expected value or an error value. -
iterator - Contains the contents of the
<iterator>header which can be reimplemented in C++98, plus some helper types to replace what would typically be covered byauto. -
numeric - A light implementation of some of the functions added to the
<numeric>header from C++11 and up. Does not include algorithms added for parallelisation purposes as C++98 has no standard concurrency primitives. -
optional - An analogue of
std::optional. -
ratio - A recreation of the standard
<ratio>header, for compile time rational arithmetic. - rc_base - A general-purpose base class for creating reference counted, COW classes. Intended for use in rc_ptr as well as other classes which seek to add ref-counting, COW behaviour.
- rc_ptr - A reference-counted copy-on-write smart pointer.
-
scoped_ptr - A unique-owning, scope-local smart pointer. An analogue of
std::unique_ptr, but of course without move semantics. -
static_assert - A basic but effective replacement for the
static_assertkeyword. -
string_view - A recreation of C++17's
std::string_viewtemplate. -
typeindex - A recreation of the
<type_index>header for comparison ofstd::type_infoobjects. -
type_traits - A recreation of many of the standard type traits from the
<type_traits>header. -
utility - A recreation of the modern features in the
<utility>header.
While the interface of these headers and tool should match their standard library counterparts closely, individual pages are available to summarise the core features
There is also a page on Borland string handling.
-
Naming Convention - I choose the names of the headers, functions, and types here carefully. Those which meet the same contract as their standard library counterpart (minus things which are impossible to implement in C++98) share the name. So if you
#include "cpp98/utility.h"in C++98 you can be confident that you will have the same functionality as if you had included<utility>in modern C++. Whereas, types where this is not the case have different names. Considerstd::unique_ptrandscoped_ptr. The fact thatstd::unique_ptris moveable is an important part of its contract, and what makes it what it is.scoped_ptris not moveable. It can't be, we're in C++98. So, it takes a different name, as a reminder to the user that they may need to take extra care should they find themselves in C++11 and wanting to swap out pointers. -
Swapping - Where appropriate, all types here support a
swap()function. This is usually provided as both a member function for type, and a free function innamespace dp. ADL and the "two step swap" should be sufficient (as was convention in C++98). The decision was made not to specialisestd::swapfor many of these types for one simple reason - these types are all templates. While it is perfectly legal in C++98 to specialisestd::swapfor concrete types, specialising it for templates constitutes partial specialisation of a function, which should be illegal per your compiler anyway; but even if not constitutes adding a new name tonamespace std. As such, the pedantically and technically correct approach is to simply not do it. -
Modern vs Older Styles - Where possible, I include the interface of a given header as it is in the most modern C++ standard at time of writing. However, this is a C++98 library, it's written for people using C++98 (and due to the lack of
constexpralone I wouldn't recommend its use in modern standards), and ultimately, it caters to that style. Smart pointers will still have constructors forstd::auto_ptr, even if it was removed in C++17. I still use a pre-C++20 allocator syntax because the reason it was removed was largely due to language features which I don't have in C++98 replacing its use. The point I'm approaching is - if you're in C++98 and writing code in a C++98 style, this library should allow you to use modern features and classes as if they had been a part of the standard from the beginning. But if you're in modern C++, there's a good chance this library will take a suboptimal route from A to B in light of all the more modern language features which are available.
-
Why do you not have X from Y header? When adding a new feature or header I strive to include every part of that header in modern code which I reasonably can. Many library features in modern C++ rely on modern core language features or compiler magic, and so these are not included in the repo. There are also many features in the modern C++ standard library which are perfectly possible to implement in C++98, and these are what I include.
-
What about range_functions and similar "collection" headers? The C++ standard library often mandates that some functions be included in several different headers. Take the range functions for example - as of C++23,
std::beginis specified to appear in 14 different standard library headers, and none of them exclusively hold those functions. It seemed better to give such headers their own names rather than to use a name of a header which is irrelevant to the contents of the file, or a name which promises more features than I was ultimately able to include.