
Instead of the preface
Perhaps this story should begin any story about
boost ,
Loki , independent, and also supplied with the compilers implementations of the standard library C ++.
Yes, yes, and if you thought that the developers of the standard library for the same g ++, clang, Visual Studio, or God forgive me, C ++ Builder (formerly Borland, and the current Embarcadero) are gurus that do not cram down crutches, do not break the standard under their compiler and do not write bicycles, then, most likely, you are not actively using the standard C ++ library as you thought.
The article is written as a story, and contains a lot of “water” and digressions, but I hope that my experience and the resulting code will be useful for those who have encountered similar problems when developing in C ++, especially on older compilers. Link to GitHub with the result today for the impatient and non-readers:
')
https://github.com/oktonion/stdex (commits and constructive criticism are welcome)
And now, first things first.
Table of contents
Introduction
Chapter 1. Viam supervadet vadensChapter 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endifChapter 3. Finding the perfect implementation of nullptrChapter 4. C ++ Template "Magic"....
4.1 Starting small....
4.2 How many we get wonderful errors are compiled by the log....
4.3 Pointers and all-all-all....
4.4 What else is needed for the template libraryChapter 5
...
Introduction
The year was 2017, C ++ 11 has long been burst into a new stream of new and relatively new compilers, bringing standardized work with threads, mutexes, expanded template programming and standardized approaches to it, there are “big” types
long long in the standard , finally got rid of the ubiquitous need to output types for the compiler using
auto (goodbye
std :: map <type, type> :: const_iterator it = ... - well, you understand me), and a bunch of this possibility with the new
for each has become one of the most common used loop implementations by iterators. Finally, we (the developers) were able to humanly tell the user (developer) why the code is not collected using
static_assert , as well as
enable_if , that selected the required overloads now as if by magic.
It was 2017 in the yard! Already C ++ 17 was actively introduced into GCC, clang, Visual Studio, everywhere there was
decltype (since C ++ 11),
constexpr (since C ++ 11, but significantly improved), modules are almost on the way, there was a good time. I was at work, and with some disapproval, I looked at the next Internal Compiler Error in my Borland C ++ Builder 6.0, as well as at the many build errors with the next version of the boost library. I think now you understand where this craving for cycling has come from. We used Borland C ++ Builder 6.0 and Visual Studio 2010 under Windows, g ++ version 4.4.2 or lower under
QNX and under some unix systems. We were spared from MacOS, which undoubtedly was a plus. There could be no talk of any other compilers (including C ++ 11), for reasons that we leave outside of this article.
“And what could be so complicated there?” - the thought crept into my exhausted attempts to start a boost under the good old builder's brain. “I just need
type_traits ,
thread ,
mutex , maybe
chrono ,
nullptr would be nice,
too ,” I reasoned and set to work.
Chapter 1. Viam supervadet vadens
It was necessary to start somewhere, and start somewhere. Naturally, I had a number of header files and source files scattered around projects with implementations of a similar or identical functionality from the C ++ 11 standard library of my development, as well as honestly borrowed or reworked from codes same gcc and boost. Combining all this together, I got some porridge from functions, classes, macros, which was supposed to turn into an elegant and slim standard library. Assessing the amount of work, I immediately decided to abandon the implementation of everything and everything, limiting myself to developing the “add-on” over the standard C ++ 98 library supplied with the compiler.
In the initial version there was no special adherence to the standard, mainly applied tasks were solved. For example,
nullptr looked like this:
#define nullptr 0
static_assert was solved too simply:
#define STATIC_ASSERT(expr) typedef int test##__LINE__##[expr ? 1 : -1];
std :: to_string was implemented via
std :: stringstream , which was replaced by
std :: strstream in implementations without the
sstream header file, all of which was shoved immediately into the
namespace std :
#ifndef NO_STD_SSTREAM_HEADER #include <sstream> #else #include <strstream> namespace std {typedef std::strstream stringstream;} #endif namespace std { template<class T> string to_string(const T &t) { stringstream ss; ss << t; return ss.str(); } }
There were also "tricks" that are not included in the standard, but nevertheless useful in everyday work, such as the
forever or
countof macros :
#define forever for(;;)
countof then transformed into a more C ++ version:
template <typename T, std::size_t N> char(&COUNTOF_REQUIRES_ARRAY_ARGUMENT(T(&)[N]))[N];
Work with threads (header file from std) was implemented through some of the Tiny libraries, rewritten taking into account the features of the entire compiler zoo and OS. And unless
type_traits to some extent was already similar to what the C ++ standard required. 11. There was
std :: enable_if ,
std :: integral_constant ,
std :: is_const, and similar templates that were already used in development.
namespace std { template<bool Cond, class Iftrue, class Iffalse> struct conditional { typedef Iftrue type; };
It was decided to allocate all non-standard and “compiler” macros, functions, types into a separate header file
core.h. And, contrary to the practice of boost, where "switching" implementations with the help of macros are commonly used, the macros related to compiler-
dependent things in all library files, except
core.h, should be abandoned to the maximum . Also, the functionality that can not be implemented without the use of "hacks" (violation of the standard, relying that Undefined Behavior will be Somewhat Defined), or implemented individually for each compiler (through its build-in macros for example), it was decided not to add to the library, so as not to produce one more monstrous (but wonderful) boost. As a result, the main and practically the only thing
core.h is used for is to determine if the built-in
nullptr supports (because compilers swear if they override the reserved words), support the built-in
static_assert (again to avoid overlapping the reserved word) and support the built-in C ++ types 11
char16_t and
char32_t .
Looking ahead, I can say that the idea almost succeeded, because most of the fact that boost is determined depending on the specific compiler by hard macros, in this implementation it is determined by the compiler itself at the compilation stage.
The end of the first chapter. In the
second chapter, I will continue the story about the difficulties of dealing with compilers, about the found crutches and elegant solutions in the depths of gcc, boost and Visual Studio, as well as describing my impressions of what I saw and the experience gained with code examples.
Thank you for attention.