-
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. - shared_ptr - A reference counted, copyable smart pointer which automatically cleans up its resource when the last instance is destroyed.
-
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. -
Forward Compatibility - 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 fromstd::auto_ptrbecause 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. DefiningDP_CPPXXwhereXXis your current version (11, 14, 17, 20, 23) will suppress any code which was deprecated or removed from the standard as of that update. Note that this does not mean that I add future-compatible alternatives, like using it to mark functionsconstexprfor each standard, or so on. 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.
-
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.