Как я стандартную библиотеку C++11 писал или почему boost такой страшный. Глава 2

    Да - да, вот с этим девизом я и ринулся в бой.

    Краткое содержание предыдущих частей


    Из-за ограничений на возможность использовать компиляторы C++ 11 и от безальтернативности boost'у возникло желание написать свою реализацию стандартной библиотеки C++ 11 поверх поставляемой с компилятором библиотеки C++ 98 / C++ 03.

    Помимо стандартных заголовочных файлов type_traits, thread, mutex, chrono так же были добавлены nullptr.h реализующий std::nullptr_t и core.h куда были вынесены макросы, относящиеся к компиляторозависимому функционалу, а так же расширяющие стандартную библиотеку.

    Ссылка на GitHub с результатом на сегодня для нетерпеливых и нечитателей:

    Коммиты и конструктивная критика приветствуются

    Оглавление


    Введение
    Глава 1. Viam supervadet vadens
    Глава 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endif
    Глава 3. Поиск идеальной реализации nullptr
    Глава 4. Шаблонная «магия» C++
    ....4.1 Начинаем с малого
    ....4.2 О сколько нам ошибок чудных готовит компиляций лог
    ....4.3 Указатели и все-все-все
    ....4.4 Что же еще нужно для шаблонной библиотеки
    Глава 5.


    Глава 2. #ifndef __CPP11_SUPPORT__ #define __COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endif


    После того как весь код был немного причесан и разделен по «стандартным» заголовкам в отдельный namespace stdex я приступил к наполнению type_traits, nullptr.h и попутно того самого core.h, в котором содержались макросы для определения версии стандарта, используемого компилятором и поддержки им «нативных» nullptr, char16_t, char32_t и static_assert.

    В теории все просто — согласно стандарту C++ (п.14.8) макрос __cplusplus должен быть определен компилятором и соответствовать версии поддерживаемого стандарта:

    C++ pre-C++98:  #define __cplusplus 1
    C++98:          #define __cplusplus 199711L
    C++98 + TR1:    #define __cplusplus 199711L // ???
    C++11:          #define __cplusplus 201103L
    C++14:          #define __cplusplus 201402L
    C++17:          #define __cplusplus 201703L
    

    соответственно код для определения наличия поддержки тривиален:

    #if (__cplusplus >= 201103L) // стандарт C++ 11 или выше
        #define _STDEX_NATIVE_CPP11_SUPPORT // есть поддержка 11 стандарта (nullptr, static_assert)
        #define _STDEX_NATIVE_CPP11_TYPES_SUPPORT // есть встроенные типы char16_t, char32_t
    #endif
    

    image На деле не все так просто и теперь начинаются интересные костыли с граблями.

    Во-первых не все, а точнее сказать ни один, из компиляторов не реализуют очередной стандарт полностью и сразу. К примеру в Visual Studio 2013 отсутствовал constexpr очень долгое время, при этом утверждалось что C++11 она поддерживает — с оговорочкой, что реализация не полная. То есть auto — пожалуйста, static_assert — так же легко (еще с более ранних MS VS), а вот constexprнет. Во-вторых не все компиляторы (и это удивляет еще больше) верно выставляют данный define и своевременно его обновляют. Неожиданно в том же самом компиляторе Visual Studio не изменяли версию дефайна __cplusplus аж с самых первых версий компилятора, хотя давно уже заявлена полная поддержка C++ 11 (что тоже не правда, за что им отдельные лучи недовольства — как только разговор заходит о конкретной функциональности «нового» 11 стандарта разработчики сразу же говорят что нет C99 preprocessor, еще других «фич»). И ситуация еще усугубляется тем, что по стандарту компиляторам разрешено выставлять данный define в отличные от приведенных выше значений, если они не до конца соответствуют заявленным стандартам. Логично было бы предположить к примеру такое развитие дефайнов для данного макроса (с вводом нового функционала увеличивать и число, скрывающееся за данным define):

    standart C++98:          #define __cplusplus 199711L // C++98
    standart C++98 + TR1:    #define __cplusplus 200311L // C++03
    nonstandart C++11:       #define __cplusplus 200411L // C++03 + auto and dectype
    nonstandart C++11:       #define __cplusplus 200511L // C++03 + auto, dectype and constexpr(partly)
    ...
    standart C++11:          #define __cplusplus 201103L // C++11
    

    Но при этом из основных популярных компиляторов никто не «запаривается» с данной возможностью.

    Из-за всего этого (не побоюсь этого слова) бардака теперь для каждого нестандартного компилятора приходится писать свои специфичные проверки с целью узнать какой стандарт C++ и в каком объеме он поддерживает. Хорошая новость в том что нам нужно узнать о всего лишь несколько функциях компилятора для корректной работы. Во-первых теперь мы добавляем проверку версии для Visual Studio через уникальный для этого компилятора макрос _MSC_VER. Так как в моем арсенале поддерживаемых компиляторов есть еще и C++ Borland Builder 6.0, разработчики которого в свою очередь очень стремились сохранить совместимость с Visual Studio (в том числе и с ее «особенностями» и багами), то там тоже внезапно есть данный макрос. Для clang-совместимых компиляторов имеется нестандартный макрос __has_feature(feature_name), который позволяет узнать о наличии поддержки компилятором той или иной функциональности. В итоге код раздувается до:

    #ifndef __has_feature
    	#define __has_feature(x) 0 // Compatibility with non-clang compilers.
    #endif
    
    // Any compiler claiming C++11 supports, Visual C++ 2015 and Clang version supporting constexpr
    #if ((__cplusplus >= 201103L) || (_MSC_VER >= 1900) || (__has_feature(cxx_constexpr))) // C++ 11 implementation
    	#define _STDEX_NATIVE_CPP11_SUPPORT
    	#define _STDEX_NATIVE_CPP11_TYPES_SUPPORT
    #endif
    

    Хочется охватить больше компиляторов? Добавляем проверки для Codegear C++ Builder, который является наследником Borland (в самых худших его проявлениях, но об этом позже):

    #ifndef __has_feature
        #define __has_feature(x) 0 // Compatibility with non-clang compilers.
    #endif
    
    // Any compiler claiming C++11 supports, Visual C++ 2015 and Clang version supporting constexpr
    #if ((__cplusplus >= 201103L) || (_MSC_VER >= 1900) || (__has_feature(cxx_constexpr))) // C++ 11 implementation
        #define _STDEX_NATIVE_CPP11_SUPPORT
        #define _STDEX_NATIVE_CPP11_TYPES_SUPPORT
    #endif
    
    #if !defined(_STDEX_NATIVE_CPP11_TYPES_SUPPORT)
        #if ((__cplusplus > 199711L) || defined(__CODEGEARC__))
            #define _STDEX_NATIVE_CPP11_TYPES_SUPPORT
        #endif
    #endif
    

    Стоит так же отметить, что так как в Visual Studio уже реализована поддержка nullptr с версии компилятора _MSC_VER 1600, так же как и встроенных типов char16_t и char32_t, то нам необходимо это корректно обработать. Еще немного проверок добавлено:

    #ifndef __has_feature
        #define __has_feature(x) 0 // Compatibility with non-clang compilers.
    #endif
    
    // Any compiler claiming C++11 supports, Visual C++ 2015 and Clang version supporting constexpr
    #if ((__cplusplus >= 201103L) || (_MSC_VER >= 1900) || (__has_feature(cxx_constexpr))) // C++ 11 implementation
        #define _STDEX_NATIVE_CPP11_SUPPORT
        #define _STDEX_NATIVE_CPP11_TYPES_SUPPORT
    #endif
    
    #if !defined(_STDEX_NATIVE_CPP11_TYPES_SUPPORT)
        #if ((__cplusplus > 199711L) || defined(__CODEGEARC__))
            #define _STDEX_NATIVE_CPP11_TYPES_SUPPORT
        #endif
    #endif
    
    #if ((!defined(_MSC_VER) || _MSC_VER < 1600) && !defined(_STDEX_NATIVE_CPP11_SUPPORT))
        #define _STDEX_IMPLEMENTS_NULLPTR_SUPPORT
    #else
        #define _STDEX_NATIVE_NULLPTR_SUPPORT
    #endif
    
    #if (_MSC_VER >= 1600)
        #ifndef _STDEX_NATIVE_CPP11_TYPES_SUPPORT
            #define _STDEX_NATIVE_CPP11_TYPES_SUPPORT
        #endif
    #endif
    

    Заодно мы еще проверим на поддержку C++ 98, так как для компиляторов без нее не будет существовать некоторых заголовочных файлов стандартной библиотеки, а проверить отсутствие оных мы никак не можем средствами компилятора.

    Полный вариант
    #ifndef __has_feature
        #define __has_feature(x) 0 // Compatibility with non-clang compilers.
    #endif
    
    // Any compiler claiming C++11 supports, Visual C++ 2015 and Clang version supporting constexpr
    #if ((__cplusplus >= 201103L) || (_MSC_VER >= 1900) || (__has_feature(cxx_constexpr))) // C++ 11 implementation
        #define _STDEX_NATIVE_CPP11_SUPPORT
        #define _STDEX_NATIVE_CPP11_TYPES_SUPPORT
    #endif
    
    #if !defined(_STDEX_NATIVE_CPP11_TYPES_SUPPORT)
        #if ((__cplusplus > 199711L) || defined(__CODEGEARC__))
            #define _STDEX_NATIVE_CPP11_TYPES_SUPPORT
        #endif
    #endif
    
    #if ((!defined(_MSC_VER) || _MSC_VER < 1600) && !defined(_STDEX_NATIVE_CPP11_SUPPORT))
        #define _STDEX_IMPLEMENTS_NULLPTR_SUPPORT
    #else
        #define _STDEX_NATIVE_NULLPTR_SUPPORT
    #endif
    
    #if (_MSC_VER >= 1600)
        #ifndef _STDEX_NATIVE_CPP11_TYPES_SUPPORT
            #define _STDEX_NATIVE_CPP11_TYPES_SUPPORT
        #endif
    #endif
    
    #if _MSC_VER // Visual C++ fallback
        #define _STDEX_NATIVE_MICROSOFT_COMPILER_EXTENSIONS_SUPPORT
        #define _STDEX_CDECL __cdecl
    
        #if (__cplusplus >= 199711L)
            #define _STDEX_NATIVE_CPP_98_SUPPORT
        #endif
    #endif
    
    // C++ 98 check:
    #if ((__cplusplus >= 199711L) && ((defined(__INTEL_COMPILER) || defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4))))))    
        #ifndef _STDEX_NATIVE_CPP_98_SUPPORT
            #define _STDEX_NATIVE_CPP_98_SUPPORT
        #endif    
    #endif
    


    И вот уже во всю начинают возникать в памяти объемные конфиги из boost в которых множество трудолюбивых разработчиков выписывали все эти компиляторозависимые макросы и из них составляли карту того, что поддерживается, а что нет конкретным компилятором конкретной версии, от которых мне лично становится не по себе, хочется никогда на это не смотреть и не трогать больше. Но хорошая новость в том, что на этом можно остановиться. По крайней мере мне этого достаточно для поддержки большинства популярных компиляторов, но если вы нашли неточность или хотите добавить еще один компилятор — я буду только рад принять pull request.

    Большим достижением по сравнению с boost я считаю то, что удалось сдержать расползание компиляторозависимых макросов по коду, что делает код чище и проще для понимания, а так же не нагромождать десятки конфигурационных файлов для каждой из ОС и для каждого компилятора. О недостатках данного подхода мы поговорим чуть позже.

    На данном этапе мы уже можем начать подключать недостающую функциональность из 11 стандарта, и первое что мы введем это static_assert.

    static_assert


    Определим структуру StaticAssertion, которая будет принимать шаблонным параметром булевское значение — там будет наше условие, при невыполнении которого (выражение приводится к false) произойдет ошибка компиляции неспециализированного шаблона. И еще одну структуру-пустышку для приема sizeof(StaticAssertion).

    namespace stdex
    {
        namespace detail {
    
            template <bool>
            struct StaticAssertion;
    
            template <>
            struct StaticAssertion<true>
            {
            }; // StaticAssertion<true>
    
            template<int i>
            struct StaticAssertionTest
            {
            }; // StaticAssertionTest<int>
        }
    }
    

    и далее магия макросов

    #ifdef _STDEX_NATIVE_CPP11_SUPPORT
        #define STATIC_ASSERT(expression, message) static_assert((expression), #message)
    #else // no C++11 support
        #define CONCATENATE(arg1, arg2)   CONCATENATE1(arg1, arg2)
        #define CONCATENATE1(arg1, arg2)  CONCATENATE2(arg1, arg2)
        #define CONCATENATE2(arg1, arg2)  arg1##arg2
    
        #define STATIC_ASSERT(expression, message)\
        struct CONCATENATE(__static_assertion_at_line_, __LINE__)\
        {\
            stdex::detail::StaticAssertion<static_cast<bool>((expression))> CONCATENATE(CONCATENATE(CONCATENATE(STATIC_ASSERTION_FAILED_AT_LINE_, __LINE__), _WITH__), message);\
        };\
        typedef stdex::detail::StaticAssertionTest<sizeof(CONCATENATE(__static_assertion_at_line_, __LINE__))> CONCATENATE(__static_assertion_test_at_line_, __LINE__)
    
        #ifndef _STDEX_NATIVE_NULLPTR_SUPPORT
            #define static_assert(expression, message) STATIC_ASSERT(expression, ERROR_MESSAGE_STRING)
        #endif
    #endif
    

    использование:

    STATIC_ASSERT(sizeof(void*) == 4, non_x32_platform_is_unsupported);
    

    Важное отличие моей реализации от стандартной в том что отсутствует перегрузка данного ключевого слова без сообщения пользователю. Это связанно с тем что в C++ невозможно определить несколько дефайнов с разным количеством аргументов но одним именем, а реализация без сообщения намного менее полезна чем выбранный вариант. Эта особенность приводит к тому что по сути STATIC_ASSERT в моей реализации это версия, добавленная уже в C++ 11.
    Разберемся по порядку что же произошло. В результате проверок версий __cplusplus и нестандартных макросов компиляторов мы имеем информацию о поддержке C++ 11 в достаточном нам объеме (а значит и static_assert), выраженную дефайном _STDEX_NATIVE_CPP11_SUPPORT. Следовательно если этот макрос определен мы можем просто использовать стандартный static_assert:

    #ifdef _STDEX_NATIVE_CPP11_SUPPORT
        #define STATIC_ASSERT(expression, message) static_assert((expression), #message)
    

    Обратите внимание что второй параметр макроса STATIC_ASSERT совсем не string literal и потому с помощью оператора препроцессора # мы преобразуем параметр message в строку для передачи в стандартный static_assert.
    Если же поддержки от компилятора у нас нет, то переходим к своей реализации. Для начала объявим вспомогательные макросы для «склеивания» строк (оператор препроцессора ## как раз отвечает за это).

    #define CONCATENATE(arg1, arg2)   CONCATENATE1(arg1, arg2)
    #define CONCATENATE1(arg1, arg2)  CONCATENATE2(arg1, arg2)
    #define CONCATENATE2(arg1, arg2)  arg1##arg2
    

    Я специально не использовал просто #define CONCATENATE(arg1, arg2 ) arg1##arg2 для того чтобы иметь возможность передавать внутрь макроса как параметр arg1 и arg2 результат того же самого макроса CONCATENATE.
    Далее объявляем структуру с красивым именем __static_assertion_at_line_{№ строки} (макрос __LINE__ так же определен стандартом и должен раскрываться в номер строки на которой он был вызван), а внутри этой структуры добавляем поле нашего типа StaticAssertion с именем STATIC_ASSERTION_FAILED_AT_LINE_{№ строки}_WITH__{текст сообщения ошибки от вызывающего макрос}.

    #define STATIC_ASSERT(expression, message)\
    struct CONCATENATE(__static_assertion_at_line_, __LINE__)\
    {\
        stdex::detail::StaticAssertion<static_cast<bool>((expression))> CONCATENATE(CONCATENATE(CONCATENATE(STATIC_ASSERTION_FAILED_AT_LINE_, __LINE__), _WITH__), message);\
    };\
    typedef stdex::detail::StaticAssertionTest<sizeof(CONCATENATE(__static_assertion_at_line_, __LINE__))> CONCATENATE(__static_assertion_test_at_line_, __LINE__)
    

    Шаблонным параметром в StaticAssertion передадим выражение, которое проверяется в STATIC_ASSERT, приведя его к bool. И в завершение для того чтобы избежать создания локальных переменных и осуществить zero-overhead проверку пользовательского условия объявляется псевдоним для типа StaticAssertionTest<sizeof({имя объявленной выше структуры}) с именем __static_assertion_test_at_line_{№ строки}.

    Вся красота с именованиями нужна лишь для того чтобы из ошибки компиляции было понятно что это результат assert, а не просто ошибка, а так же чтобы вывести сообщение об ошибке, которое было заданно для этого assert. Трюк с sizeof необходим чтобы заставить компилятор инстанцировать шаблонный класс StaticAssertion, находящийся внутри только что объявленной структуры, и таким образом проверить переданное в assert условие.

    Результаты выдачи STATIC_ASSERT
    GCC:
    30:103: error: field 'STATIC_ASSERTION_FAILED_AT_LINE_36_WITH__non_x32_platform_is_unsupported' has incomplete type 'stdex::detail::StaticAssertion<false>'
    25:36: note: in definition of macro 'CONCATENATE2'
    23:36: note: in expansion of macro 'CONCATENATE1'
    30:67: note: in expansion of macro 'CONCATENATE'
    24:36: note: in expansion of macro 'CONCATENATE2'
    23:36: note: in expansion of macro 'CONCATENATE1'
    30:79: note: in expansion of macro 'CONCATENATE'
    24:36: note: in expansion of macro 'CONCATENATE2'
    23:36: note: in expansion of macro 'CONCATENATE1'
    30:91: note: in expansion of macro 'CONCATENATE'
    36:3: note: in expansion of macro 'STATIC_ASSERT'

    Borland C++ Builder:
    [C++ Error] stdex_test.cpp(36): E2450 Undefined structure 'stdex::detail::StaticAssertion<0>'
    [C++ Error] stdex_test.cpp(36): E2449 Size of 'STATIC_ASSERTION_FAILED_AT_LINE_36_WITH__non_x32_platform_is_unsupported' is unknown or zero
    [C++ Error] stdex_test.cpp(36): E2450 Undefined structure 'stdex::detail::StaticAssertion<0>'

    Visual Studio:
    Error C2079 'main::__static_assertion_at_line_36::STATIC_ASSERTION_FAILED_AT_LINE_36_WITH__non_x32_platform_is_unsupported' uses undefined struct 'stdex::detail::StaticAssertion<__formal>' stdex_test c:\users\user\documents\visual studio 2015\projects\stdex_test\stdex_test\stdex_test.cpp 36


    Вторая «фишка», которую хотелось иметь, при этом отсутствующая в стандарте это countof — подсчет количества элементов в массиве. Сишники очень любят данный макрос объявлять через sizeof(arr) / sizeof(arr[0]), но мы пойдем дальше.

    countof


    #ifdef _STDEX_NATIVE_CPP11_SUPPORT
    
    #include <cstddef>
    namespace stdex
    {
        namespace detail
        {
            template <class T, std::size_t N>
            constexpr std::size_t _my_countof(T const (&)[N]) noexcept
            {
                return N;
            }
        } // namespace detail
    }
    #define countof(arr) stdex::detail::_my_countof(arr)
    
    #else //no C++11 support
    
    #ifdef _STDEX_NATIVE_MICROSOFT_COMPILER_EXTENSIONS_SUPPORT // Visual C++ fallback
    #include <stdlib.h>
    #define countof(arr) _countof(arr)
    
    #elif defined(_STDEX_NATIVE_CPP_98_SUPPORT)// C++ 98 trick
    #include <cstddef>
    template <typename T, std::size_t N>
    char(&COUNTOF_REQUIRES_ARRAY_ARGUMENT(T(&)[N]))[N];
        
    #define countof(x) sizeof(COUNTOF_REQUIRES_ARRAY_ARGUMENT(x))
    #else
    #define countof(arr) sizeof(arr) / sizeof(arr[0])
    #endif
    

    Для компиляторов с поддержкой constexpr объявим constexpr-версию данного шаблона (что совершенно не обязательно, на самом деле для всех стандартов достаточно реализации через шаблон COUNTOF_REQUIRES_ARRAY_ARGUMENT), для остальных же введем версию через шаблонную функцию COUNTOF_REQUIRES_ARRAY_ARGUMENT. Visual Studio здесь снова отличилась наличием собственной реализации _countof в заголовочном файле stdlib.h.

    Функция COUNTOF_REQUIRES_ARRAY_ARGUMENT выглядит устрашающе и разобраться в том что она делает довольно непросто. Если присмотреться, то можно понять что она принимает на вход единственным аргументом массив элементов шаблонного типа T и размера N — таким образом в случае передачи других типов элементов (не массивов) мы получим ошибку компиляции, что несомненно радует. Присмотревшись еще внимательней можно разобраться (с трудом) что возвращает она массив элементов char размера N. Спрашивается зачем нам это все? Здесь вступает в дело оператор sizeof и его уникальные возможности работать во время компиляции. Вызов sizeof(COUNTOF_REQUIRES_ARRAY_ARGUMENT) определяет размер возвращаемого функцией массива элементов char, а так как по стандарту sizeof(char) == 1, то это и есть количество элементов N в исходном массиве. Изящно, красиво, и совершенно бесплатно.

    forever


    Еще один небольшой вспомогательный макрос, который я использую везде где необходим бесконечный цикл это forever. Определен он так:

    #if !defined(forever)
        #define forever for(;;)
    #else
        #define STRINGIZE_HELPER(x) #x
        #define STRINGIZE(x) STRINGIZE_HELPER(x)
        #define WARNING(desc) message(__FILE__ "(" STRINGIZE(__LINE__) ") : warning: " desc)
    
        #pragma WARNING("stdex library - macro 'forever' was previously defined by user; ignoring stdex macro definition")
    
        #undef STRINGIZE_HELPER
        #undef STRINGIZE
        #undef WARNING
    #endif
    

    пример синтаксиса для определения явного бесконечного цикла:

        unsigned int i = 0;
        forever
        {
            ++i;
        }
    

    Данный макрос используется исключительно для явного определения бесконечного цикла и включен в библиотеку только из соображений «добавить синтаксического сахара». В дальнейшем предполагаю его заменить на опционально через define подключаемый макрос FOREVER. Что же примечательно в вышеприведенном отрывке кода из библиотеки, так это тот самый макрос WARNING, который генерирует сообщение-предупреждение во всех компиляторах если макрос forever уже был определен пользователем. Он использует уже знакомый стандартный макрос __LINE__ и так же стандартный __FILE__, который преобразуется в строку с именем текущего исходного файла.

    stdex_assert


    Для реализации assert в рантайме введен макрос stdex_assert как:

    #if defined(assert)
    #ifndef NDEBUG
    	#include <iostream>
    	#define stdex_assert(condition, message) \
    	do { \
    		if (! (condition)) { \
    			std::cerr << "Assertion `" #condition "` failed in " << __FILE__ \
    					  << " line " << __LINE__ << ": " << message << std::endl; \
    			std::terminate(); \
    		} \
    	} while (false)
    #else
    	#define stdex_assert(condition, message) ((void)0)
    #endif
    #endif
    

    Не скажу что я очень горжусь данной реализацией (будет изменена в будущем), но здесь использован интересный прием на который хочется обратить внимание. Для того чтобы скрыть проверки из области видимости кода приложения используется конструкция do {} while(false), которая выполнится, что очевидно, один раз и при этом не внесет «служебного» кода в общий код приложения. Данный прием довольно полезен и применяется еще в нескольких местах библиотеки.

    В остальном же реализация очень похожа на стандартный assert — при определенном макросе NDEBUG, который обычно выставляют компиляторы в релизных сборках, assert ничего не делает, в противном случае прерывает выполнение программы с выводом сообщения в стандартный поток ошибок при несоблюдении условия assert'а.

    noexcept


    Для функций, которые не бросают исключений, в новом стандарте было введено ключевое слово noexcept. Его тоже довольно просто и безболезненно можно реализовать через макрос:

    #ifdef _STDEX_NATIVE_CPP11_SUPPORT
        #define stdex_noexcept noexcept
    #else
        #define stdex_noexcept throw()
    #endif
    

    однако необходимо понимать что по стандарту noexcept может принимать значение bool, а так же использоваться для определения во время компиляции что переданное ему выражение не бросает исключений. Данный функционал не может быть реализован без поддержки компилятора, и потому в библиотеке есть только «урезанный» stdex_noexcept.

    Конец второй главы. В третьей главе будет рассказано о тонкостях реализации nullptr, о том почему он разный для разных компиляторов, а так же как продвигалась разработка type_traits и на какие еще баги в компиляторах я наткнулся при ее разработке.

    Благодарю за внимание.
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 20

      0
      Пара существенных замечаний.
      1. Макросы без семантики вызова функций (особенно маленькими буквами) надо выжигать калёным железом. У вас бомба такая же, как min()/max() в заголовках Windows SDK. Достаточно написать где-нибудь:
      bool forever = IsForever();

      или зацепить заголовок с объявлением:
      void Foo(bool forever = false);

      И начнётся мат. Сначала компилятора в stderr, потом разработчика в ваш адрес. :)

      2. Конструкции типа while(true)/while(false) не проходят через высокий уровень варнингов — компилятор начинает ругаться в стиле «Condition is always true(false)» Если включена опция «варнинги как ошибки», то такая ругань начнёт сильно раздражать. Поэтому лучше такое не использовать, либо сразу включать подавление этих варнингов (что не есть хорошо).
        0

        Благодарю за конструктивную критику.
        По п.1: полностью согласен, это действительно скорее мое баловство с добавлением "вкусного" лично для моих нужд синтаксического сахара. Это что касается forever. С min()/max() как макросами я тоже уже голову поломал, как раз когда ratio делал и вполне разделяю негодование по поводу лишних макросов. За countof я бы поборолся, т.к. он часто используемый шаблон (и в VS с маленькой определен хоть и с префиксом). Что касается остальных ключевых слов-макросов то думаю здесь ясно почему я вынужден реализовать так и только так.
        По п.2: там и так варнинги будут (потом поясню почему), но в целом да — я работаю на их тотальное уничтожение и конкретно этот кусочек думаю можно смело заменить.

        0
        Важное отличие моей реализации от стандартной в том что отсуствует перегрузка данного ключевого слова без сообщения пользователю. Это связанно с тем что в C++ невозможно определить несколько дефайнов с разным количеством аргументов но одним именем, а реализация без сообщения намного менее полезна чем выбранный вариант. Эта особенность приводит к тому что по сути STATIC_ASSERT в моей реализации это версия, добавленная уже в C++ 17.

        А использовать variadic macro возможности нет? Я в курсе, что официально они появились только в С++11, но многие компиляторы их поддерживают.
          0
          Угу. Поддерживают, только каждый в своём стиле. Даже после официального появления в C++11.
            0
            #define PR(f, ...) printf(f, __VA_ARGS__) 
            

            Такой стиль поддерживают более менее все.
              +1
              Угу. Всё будет хорошо ровно до тех пор, пока в __VA_ARGS__ будет хотя бы один аргумент. Если же он окажется пуст, то начнутся приключения на ровном месте.
                0
                Можно же так:

                #define PR(f, ...) printf(f, ##__VA_ARGS__)

                или

                #define VA_ARGS(...) , ##__VA_ARGS__
                #define PR(f, ...) printf(f, VA_ARGS(__VA_ARGS__))
                  0
                  Первый вариант — gcc-specific. Второй — не уверен, что вообще будет работать. Только в C++20 появится __VA_OPT__(), который раскрывает аргумент только в случае, если эллипсис — не пустой. Такие дела.
            0
            Вы в своем же комментарии ответили на свой вопрос. Возможности проверить поддерживает ли компилятор variadic macro нет, только перебирать конкретные версии конкретных компиляторов макросами, чем занимается boost как раз.
            А потом, как верно указал FlexFerrum, перебирать еще и реализации этих variadic macro, т.к. каждый из компиляторов лепит их реализации кто во что горазд почему то.

            По поводу процитированного абзаца скажу что в этом нет ничего страшного, т.к., как я и написал, не вижу никакого преимущества и применения для static_assert(expr) перед static_assert(expr, message), кроме лени написать в чем ошибка то и на что проверяют.
              0
              Резонно.
            0
            do {} while(false)
            А разве нельзя заменить эту конструкцию на просто
            {}
            ? Она тоже выполнится ровно один раз и ограничит область видимости. Или это делается для того, чтобы потребовать у пользующего кода точку с запятой в конце?
            Сталкивался в легаси с верхним вариантом, он там был, в том числе, чтобы использовать break для выхода из границ.
              0
              Все верно, и это как раз те изменения которые нужно сделать как только руки дойдут до этого. По сути данная конструкция полезна в switch case блоках, а здесь она не требуется. Плюс может генерировать warning как справедливо заметили в первом комментарии.

              Я привел здесь этот кусок кода чтобы акцентировать внимание на том что такой прием есть, но конкретно из этого места его лучше выпилить.
              +2

              Тяжело читать про чужую боль. Все эти макросы как замедленные бомбы и куча разных стандартов с++ у компиляторов. Мне кажется иногда лучше написать больше кода пусть и не очень изящного основываясь на старлм стагдарте чем делать такого франкенштейна.

                0
                Могу лишь сказать, что разбираться в тонкостях работы стандартной библиотеки довольно увлекательное занятие. Что раздражает, так это несоотвествие компиляторов собственно стандарту.

                А на счет франкенштейна — считаю что по сравнению с boost у меня получилось намного чище и понятней, и все со стандартной функциональностью практически любого C++ компилятора с поддержкой C++98. А вот «некрасивая» часть локализована и возникает только от особенностей несоотвествия конкретного компилятора стандарту.
                • НЛО прилетело и опубликовало эту надпись здесь
                0

                countof в стандарте появился в версии C++17 в образе std::size()

                  +1
                  Угу. Но, как всегда, что-то где-то пошло не совсем так… :D
                  image
                    +1
                    Вот я тоже на это дело посмотрел и так и не понял зачем было мешать size() с countof(). Ну то есть понятно почему оно так отрабатывает. Но это как то… не интуитивно чтоли. Примерно как с каноническим примером из Java, где "==" внезапно работает не так как подсказывает здравый смысл.

                    Это те самые вещи, когда сначала чешешь голову, думая «что за чертовщина, ведь так не должно быть!», а потом «аааа, ну да...», и понять то ты понял почему оно так, но всеравно остается впечатление какой то бредовости такого поведения.
                      +1

                      Что то не понимаю эту функциональную моду в плюсах. Сначала было всё в обычных сишных массивах и структурах и были функции которые первым аргументом принимали те самые структуры. Потом в плюсах сделали ооп и вроде бы всё стало инкапсулировано и тому подобное, а сейчас обратно возвращают обратно обертки типа f(obj) вместо obj.f().
                      В данном случае, вроде есть наконец-то уже std:array и можно избежать путаницы с sizeof.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое