-
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;
}I provide some notes on updating from C++98 to a modern language standard and how best to replace this library with more modern tools.
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 are some headers made specifically for Borland/C++Builder types, to make their use with the rest of the library simpler and easier. As these are non-standard C++, they are found in the borland subdirectory of the main include path. They 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. -
any - A version of
std::any- a type-safe container which can store an object of any type. -
array - An analogue of
std::array, sharing the same functionality and interface. -
bit - A recreation of the bitwise functions in
<bit>. - byte - A type representing a byte, with no implicit arithmetic conversions.
-
cctype - Wrappers for the
cctypefunctions which are portable and whose address can be taken. -
expected - An analogue of
std::expected- A type which contains either an expected value or an error value. -
flat_set - An analogue of
std::flat_set- an associate container adaptor backed by contiguous storage. -
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. -
memory - A recreation of the modern
<memory>header, for high level memory manipulation. -
new - A lightweight
<new>header which containsget_new_handler(). - null_ptr - A null pointer type which will not be converted to an integer type.
-
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. - reference_wrapper - A trivially copyable wrapper around a reference type.
-
scoped_ptr - A unique-owning, scope-local smart pointer. An analogue of
std::unique_ptr, but of course without move semantics. - shared_ptr - A reference counted, copyable smart pointer which automatically cleans up its resource when the last instance is destroyed.
- span - A non-owning view over contiguous data.
-
static_assert - A basic but effective replacement for the
static_assertkeyword. -
string - Functions which have been added to the
<string>header since C++11, including those retroactively added by DR. -
string_view - A recreation of C++17's
std::string_viewtemplate. -
typeindex - A recreation of the
<typeindex>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
This library also makes some slight use of macros due to conformance issues from some of the target compilers. These macros are documented here.
Some headers for Borland/C++Builder types are also included, and documented here
This is a C++98 library. I do not recommend its use in modern C++ as it will measure up lacking when compared to what can be achieved with modern laguage features like constexpr. As such, it is written with the world of C++98 in mind, and will (very rarely) use one or two things which were deprecated or removed in a future standard. I strive to avoid this where possible, but if omitting it would be a notable absence then I do not omit it. For example, none of the functors in this library inherit from the "adaptable" functor bases in the world of pre-C++11; nor do any algorithms depend on them - it's easy enough to what's needed without them, and placing an onus on the user to add future-deprecated constructs to their own code is something I want to avoid completely. However, the smart pointer types in this library are constructible from std::auto_ptr because for better or worse that was the de facto standard smart pointer at the time.
But, I am aware that there may be some fringe case where a user is running this library on a more modern version of the standard, and for that reason I provide some macros where appropriate. Defining DP_CPPXX where XX is your current version (11, 14, 17, 20, 23) will suppress any code which was removed from the standard as of that update. These are inclusive of previous versions, so defining DP_CPP17 will block all code which was removed in C++11, C++14, and C++17. Note that deprecated but not removed constructs stay in, so std::auto_ptr constructors are not suppressed unless DP_CPP17 is defined. It will be very rare that you will need to use these, as I almost entirely avoid any future-deprecated code in the library, but if nothing else it's better to have and not need them than vice versa. No macros will be defined or will pollute the user namespace unless you specifically use one of these defines. The sole exception to this is a macro which is defined if you are on an old C++98 Borland compiler, but if you're using that compiler you have bigger problems.
Note that the presence of these macros does not mean that I add C++11 and up compatibility to the functions here, such as using it to selectively mark functions constexpr. It's a tool to stop some unrelated part of the library from divebombing your compilation, not a versioning system. And this remains a C++98 library.
Notes on updating to a modern standard can be found here
One of the compilers this library was written for and tested on was Borland's C++Builder compiler. More precisely, the compiler which powers C++Builder 10.2 (released 2018), which was the last version before Embarcadero shifted to a fork of CLang and adopted modern C++. The results of that testing is that C++98 support on that compiler is still lacking. There are certain template techniques (including a core technique which powers a large chunk of C++98 SFINAE) which are simply not supported. Consequently, a significant part of this library is not compatible with that compiler and is automatically omitted by a macro. I strive to minimise this as far as is possible, but if you are on a Borland compiler then it is unavoidable that the interface you have available will be less than is documented here. The features which are entirely absent on a Borland compiler are marked with an obelus†
I strive to test every part of this library and apply the DP_BORLAND macro to every part which will fail on the Borland compiler, but I've found that its template engine can be quite the wildcard on parsing and evaluating templates, even across similar types, so unfortunately there is the possibility that I have missed some. Feel free to report such cases in issues or put them in preprocessor block yourself.
Note that in the case where a Borland compiler is detected, it is possible for the exception types in this library to be adjusted to inherit from the System::SysUtils::Exception C++Builder class, with the reasoning being that the C++Builder library's propensity to throw exceptions when any part of any component goes wrong means you will likely be catching those kinds of exceptions anyway. The thought did occur to allow the exceptions to share an inheritance hierarchy so they could be caught by both std::exception and System::SysUtils::Exception catch blocks, but C++Builder rules on inheritance are stricter than the C++ standard, and forbid it. This can be enabled by defining DP_BORLAND_USE_BORLAND_EXCEPTIONS
-
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.
-
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.
-
Why not just use the
__cplusplusmacro to handle versioning? This is quite simple - there are compilers out there who do not define__cplusplusto the current version, either automatically or at all. Even modern giant MSVC has it expand to1on all versions by default; and the Borland compiler this library was tested on cannot set it to be a C++ language standard version at all. In those cases, I would have to write an automatic versioning macro anyway, so I provided one.