How I wrote the standard C ++ 11 library or why the boost is so scary. Introduction

Yes - yes, with this motto I rushed into battle.

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 so 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 for today for 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 vadens

Chapter 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endif
Chapter 3. Finding the perfect realization nullptr
Chapter 4. Patterned "magic» the C ++
.... 4.1 start small
.... 4.2 About how many mistakes we Wonderful compilations are prepared by the log
.... 4.3 Pointers and all-all-all
.... 4.4 What else is needed for the sample library
Chapter 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 inform the user (developer) why the code is not going to, usingstatic_assert , as well as enable_if , that selected the necessary 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 QNXand 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 sources scattered around projects with implementations of a similar or identical functionality from the standard C ++ 11 library of my development, as well as honestly borrowed or reworked from codes same gcc and boost. By 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.

The initial version did not follow the standard, mostly 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 through the std :: a stringstream , who was substituted in the std :: strstream in implementations without the header file sstream , and shoves it all at once in the namespace the std :

#ifndef NO_STD_SSTREAM_HEADER#include<sstream>#else#include<strstream>namespacestd {typedefstd::strstream stringstream;}
	#endifnamespacestd
	{
		template<classT>
		stringto_string(constT &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(;;) // бесконечный цикл объявленный явно#define countof(arr) sizeof(arr) / sizeof(arr[0]) // подсчет количества элементов в массиве в стиле C

countof then transformed into a more C ++ version:

template <typename T, std::size_t N>
		char(&COUNTOF_REQUIRES_ARRAY_ARGUMENT(T(&)[N]))[N];
		// подсчет количества элементов в массиве в стиле C++ (с проверкой аргумента на то что он массив):	#define countof(x) sizeof(COUNTOF_REQUIRES_ARRAY_ARGUMENT(x))

Threading (header file thread from std) it was implemented through some of the Tiny libraries, rewritten, taking into account all features of the zoo compilers and operating systems. And unless type_traits to some extent was already similar to what was required by the C ++ standard 11. There was std :: enable_if , std :: integral_constant , std :: is_const, and similar templates that were already used in development.

namespacestd
	{
		template<bool Cond, classIftrue, classIffalse>
		structconditional
		{typedef Iftrue type;
		};
		// Partial specialization for false.template<classIftrue, classIffalse>
		structconditional<false, Iftrue, Iffalse>
		{typedef Iffalse type;
		};
		template <bool, classT = void>
		structenable_if
		{ };
		template <classT>
		structenable_if<true, T>
		{typedef T type;
		};
		template<classTp, TpVal>
		structintegral_constant
		{// convenient template for integral constant typesstaticconst Tp value = Val;
			typedefconst Tp value_type;
			typedef integral_constant<Tp, Val> type;
		};
		typedef integral_constant<bool, true> true_type;
		typedef integral_constant<bool, false> false_type;
		template<bool Val>
		structbool_constant :public integral_constant<bool, Val>
		{ };
		template<class, class>
		structis_same :public false_type
		{ };
		template<classTp>
		structis_same<Tp, Tp> :public true_type // specialization
		{ };
	}
	// ... и еще несколько шаблонов

It was decided to allocate all non-standard and "a compiler" macros, functions, types in a separate header file core.h . And, contrary to the practice of boost, which are commonly used "change" implementations using macros possible to abandon the macro associated with kompilyatorozavisimymi things in all the library files except core.h . 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 for which core.h is usedthis is to determine if the built-in nullptr is supported (since compilers swear if you override the reserved words), support the built-in static_assert (again to avoid overlapping the reserved word) and support the built-in C ++ 11 types 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 compile time.

The end of the first chapter. In the second chapterI 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.

Also popular now: