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

    Продолжаем приключения.

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


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

    Были реализованы static_assert, noexcept, countof, а так же, после рассмотрения всех нестандартных дефайнов и особенностей компиляторов, появилась информация о функциональности, которая поддерживается текущим компилятором. Включена своя реализация nullptr, которая подбирается на этапе компиляции.

    Настало время type_traits и всей этой «особой шаблонной магии». В предыдущих частях данной главы мы рассмотрели мою реализацию базовых шаблонов стандартной библиотеки и в данной части речь пойдет про комбинацию техники SFINAE с шаблонами и немного о кодогенерации.

    Ссылка на GitHub с результатом на сегодня для нетерпеливых и нечитателей:
    Коммиты и конструктивная критика приветствуются
    Больше шаблонов C++ под катом.

    Оглавление


    Введение
    Глава 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.


    Глава 4. Шаблонная «магия» C++. Продолжение


    4.3 Указатели и все-все-все


    На данном этапе мне оставалось только получить информацию о том является ли тип массивом для std::is_array и можно было приступать к шаблонам для указателей. Реализация так же была тривиальна, однако не без допущений.

    // is_array
    template<class>
    struct is_array :
        public false_type { };
    
    template<class _Tp, std::size_t _Size>
    struct is_array<_Tp[_Size]> :
        public true_type { };
    
    /*template<class _Tp>
    struct is_array<_Tp[]>:
        public true_type { }; */
    

    Простая специализация шаблона для массивов заданной длины «отлавливает» все типы массивов, однако проблема возникает с неполным типом T[] (массив без указания длины). Дело в том что данный тип не определяется некоторыми компиляторами (C++ Builder) при специализации шаблона, и универсальное решение здесь я пока что не нашел.

    После того как библиотека была «научена» определять встроенные типы, выравнивание в памяти типов, работать с модификаторами типов и другим базовым вещам через шаблоны во время компиляции настало время указателей и ссылок.

    image В языке C++ можно выделить две группы указателей — указатели на члены класса и указатели на остальные объекты. Почему именно такое разделение важно для дальнейшей реализации стандартной библиотеки? Дело в том что указатели на члены класса имеют существенное отличие от остальных указателей наличием this, т.е. указателя на объект этого класса. И по стандарту указатели на член класса имеют отдельный синтаксис для определения, являются отдельным типом и не могут быть представлены через обычный указатель. На практике это выражается в том что размер указателя на член класса обычно больше чем размер обычного указателя (который == sizeof(void*)), т.к. для реализации виртуальных функций-членов класса, а так же хранения указателя this компиляторы обычно реализуют указатели на член класса как структуру (почитать про виртуальные ф-ии и про структуру). То как представить указатели на члены класса оставим, согласно стандарту, на усмотрение компилятора, но об этом различии в размере и представлении будем помнить рассматривая дальнейший код.

    Для определения обычного указателя на объект напишем простой шаблон is_pointer, а так же шаблон is_lvalue_reference для ссылок на объект (is_rvalue_reference отставим, т.к. до 11 стандарта как оператора &&, так и в целом move-семантики не существовало):

    namespace detail
    {
        template<class>
        struct _is_pointer_helper :
            public false_type { };
    
        template<class _Tp>
        struct _is_pointer_helper<_Tp*> :
            public true_type { };
    }
    
    // is_pointer
    template<class _Tp>
    struct is_pointer :
        public detail::_is_pointer_helper<typename remove_cv<_Tp>::type>::type
    { };
    
    // is_lvalue_reference
    template<class>
    struct is_lvalue_reference :
        public false_type { };
    
    template<class _Tp>
    struct is_lvalue_reference<_Tp&> :
        public true_type { };
    

    Здесь уже нет чего-то принципиально нового, все то же самое делалось и в предыдущих частях данной главы. Продолжим определения указателей на объекты — теперь рассмотрим указатели на функции.
    Важно понимать что функция и функция-член класса это совершенно разные сущности согласно стандарту:

    • На первую указатель будет обычный (указатель на объект), на вторую же будет указатель на член класса.

    void (*func_ptr)(int); // указатель 'func_ptr' на функцию вида 'void func(int){}'
    void (ClassType::*mem_func_ptr)(int); // указатель 'mem_func_ptr' на функцию-член класса 'ClassType' вида 'void ClassType::func(int){}'
    

    • На первую можно создать ссылку (ссылка на объект), а на вторую ссылку создать нельзя.

    void (&func_ref)(int); // ссылка 'func_ref' на функцию вида 'void func(int){}'
    //-------------------- // ссылка на функцию-член класса не определена стандартом
    
    Здесь как раз упомяну немного о кодогенерации. Так как до C++ 11 отсутствовали шаблоны с переменным числом параметров, все шаблоны где могло быть разное количество параметров определялись через специализации основного шаблона с каким ни будь большим числом параметров на входе и их инициализацией параметрами-пустышками по-умолчанию. То же самое применялось и к перегрузкам функций, т.к. макросов с переменным числом параметров тоже не было. Так как написание руками по 60-70 строк однотипных специализаций шаблона, перегрузок функций является довольно унылым и бесполезным занятием, а так же чревато тем, что можно допустить ошибку я написал простой генератор кода шаблонов и перегрузок функций для этих целей. Я решил ограничиться определением функций до 24 параметров и выглядит это в коде довольно громоздко, но просто и понятно:

    namespace detail
    {
        template <class R>
        struct _is_function_ptr_helper : false_type {};
        template <class R >
        struct _is_function_ptr_helper<R(*)()> : true_type {};
        template <class R >
        struct _is_function_ptr_helper<R(*)(...)> : true_type {};
        template <class R, class T0>
        struct _is_function_ptr_helper<R(*)(T0)> : true_type {};
        template <class R, class T0>
        struct _is_function_ptr_helper<R(*)(T0 ...)> : true_type {};
    

    ...
        template <class R, class T0, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14, class T15, class T16, class T17, class T18, class T19, class T20, class T21, class T22, class T23, class T24>
        struct _is_function_ptr_helper<R(*)(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24)> : true_type {};
        template <class R, class T0, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14, class T15, class T16, class T17, class T18, class T19, class T20, class T21, class T22, class T23, class T24>
        struct _is_function_ptr_helper<R(*)(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24 ...)> : true_type {};
    }
    

    Определим знакомые с предыдущей главы типы для техники SFINAE:

    namespace detail
    {
        // SFINAE magic
    
        typedef char _yes_type;
        struct _no_type
        {
            char padding[8];
        };
    }
    

    Еще немного макросов для удобства
    namespace detail
    {
        #define _IS_MEM_FUN_PTR_CLR \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS)); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS...)); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS) const); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS) volatile); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS) const volatile); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS...) const); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS...) volatile); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(T::*const volatile*)(ARGS...) const volatile);
    
    #ifdef _STDEX_CDECL
    		_no_type _STDEX_CDECL _is_mem_function_ptr(...);
    
    #define _IS_MEM_FUN_CDECL_PTR \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__cdecl T::*const volatile*)(ARGS)); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__cdecl T::*const volatile*)(ARGS) const); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__cdecl T::*const volatile*)(ARGS) volatile); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__cdecl T::*const volatile*)(ARGS) const volatile);
    
    #define _IS_MEM_FUN_STDCALL_PTR \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__stdcall T::*const volatile*)(ARGS)); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__stdcall T::*const volatile*)(ARGS) const); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__stdcall T::*const volatile*)(ARGS) volatile); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__stdcall T::*const volatile*)(ARGS) const volatile);
    
    #define _IS_MEM_FUN_FASTCALL_PTR \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__fastcall T::*const volatile*)(ARGS)); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__fastcall T::*const volatile*)(ARGS) const); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__fastcall T::*const volatile*)(ARGS) volatile); \
    		template <class R, class T TYPES > \
    		_yes_type _is_mem_function_ptr(R(__fastcall T::*const volatile*)(ARGS) const volatile);
    #else
    		_no_type _is_mem_function_ptr(...);
    #define _IS_MEM_FUN_CDECL_PTR
    #define _IS_MEM_FUN_STDCALL_PTR
    #define _IS_MEM_FUN_FASTCALL_PTR
    #endif
    
    #define _IS_MEM_FUN_PTR \
    		_IS_MEM_FUN_PTR_CLR \
    		_IS_MEM_FUN_CDECL_PTR \
    		_IS_MEM_FUN_STDCALL_PTR \
    		_IS_MEM_FUN_FASTCALL_PTR
    }
    


    Макросы определены для того чтобы можно было относительно удобно переопределять TYPES и ARGS дефайны как список типов и параметров, затем подставляя макрос _IS_MEM_FUN_PTR генерировать препроцессором определения для всех возможных типов функций. Так же стоит обратить внимание на то, что для компиляторов компании Microsoft важны еще соглашения о вызовах (__fastcall, __stdcall и __cdecl), т.к. с разными соглашениями функции будут разными, хотя набор аргументов и возвращаемое значение у них одно и то же. В результате вся эта грандиозная конструкция макросов используется довольно компактно:

    namespace detail
    {
        #define TYPES
        #define ARGS
        _IS_MEM_FUN_PTR
    #undef TYPES
    #undef ARGS
    
        #define TYPES , class T0
        #define ARGS T0
        _IS_MEM_FUN_PTR
    #undef TYPES
    #undef ARGS
        
        #define TYPES , class T0, class T1
        #define ARGS T0, T1
        _IS_MEM_FUN_PTR
    #undef TYPES
    #undef ARGS
    

    ...
        #define TYPES , class T0, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9, class T10, class T11, class T12, class T13, class T14, class T15, class T16, class T17, class T18, class T19, class T20, class T21, class T22, class T23, class T24
        #define ARGS T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24
        _IS_MEM_FUN_PTR
    #undef TYPES
    #undef ARGS
    
    // не забудем убрать все лишние define за собой:
    #undef _IS_MEM_FUN_PTR
    #undef _IS_MEM_FUN_PTR_CLR 		
    #undef _IS_MEM_FUN_CDECL_PTR
    #undef _IS_MEM_FUN_STDCALL_PTR
    #undef _IS_MEM_FUN_FASTCALL_PTR
    }
    

    А теперь то, ради чего все это было написано:

    namespace detail
    {
        template <class _Tp, bool _IsRef>
        struct _is_mem_function_ptr_impl
        {
            static _Tp *p;
            static const bool value = (sizeof(_is_mem_function_ptr(_is_mem_function_ptr_impl::p)) == sizeof(_yes_type));
    
            typedef typename integral_constant<bool, _is_mem_function_ptr_impl::value == bool(true)>::type type;
        };
    
        template <class _Tp>
        struct _is_mem_function_ptr_impl<_Tp, true>:
            public false_type
        {};
    
        template <class _Tp>
        struct _is_mem_function_ptr_helper:
            public _is_mem_function_ptr_impl<_Tp, is_reference<_Tp>::value>::type
        {};
    
        template <class _Tp, bool _IsMemberFunctionPtr>
        struct _is_function_chooser_impl :
            public false_type
        { };
    
        template <class _Tp>
        struct _is_function_chooser_impl<_Tp, false> :
            public _is_function_ptr_helper<_Tp*>
        { };
    
        template<class _Tp, bool _IsRef = true>
        struct _is_function_chooser :
            public false_type
        { };
    
        template <class _Tp>
        struct _is_function_chooser<_Tp, false>
        {
    
            static const bool value = _is_function_chooser_impl<_Tp, _is_mem_function_ptr_helper<_Tp>::value>::value;
        };
    }
    

    Для проверки является ли тип функцией-членом класса сначала проверяется не является ли тип ссылочным. Затем создается указатель такого типа и подставляется в функцию-пробник. Используя технику SFINAE силами компилятора подбирается необходимая из перегрузок функций-пробников для такого указателя и на основе результата сравнения с _yes_type формируется результат.

    На основе проверки на функцию-член класса пишется проверка типа на его принадлежность типу функции. Проверяем не является ли тип ссылочным, если нет, то ищем подходящую специализацию шаблонных структур-пробников для указателя этого типа, которые будут true_type для любых указателей на функции до 24 параметров.

    И теперь используем полученный результат для реализации is_function. Здесь, по той же причине что и в предыдущей части, я не смог отнаследовать эту структуру от integral_constant, так что поведение integral_constant «имитируется».

    // is_function
    template<class _Tp>
    struct is_function
    {
        static const bool value = detail::_is_function_chooser<_Tp, is_reference<_Tp>::value>::value;
    
        typedef const bool value_type;
        typedef integral_constant<bool, is_function::value == bool(true)> type;
    
        operator value_type() const
        {	// return stored value
            return (value);
        }
    
        value_type operator()() const
        {	// return stored value
            return (value);
        }
    };
    

    А для реализации is_member_function_pointer все еще проще:

    // is_member_function_pointer
    template<class _Tp>
    struct is_member_function_pointer :
        public detail::_is_mem_function_ptr_helper<typename remove_cv<_Tp>::type>::type
    { };
    

    Далее на основе этих шаблонов мы можем определить является ли тип впринципе членом класса:

    namespace detail
    {
        template<class _Tp>
        struct _is_member_object_pointer_impl1 :
            public _not_< _or_<_is_function_ptr_helper<_Tp>, _is_mem_function_ptr_helper<_Tp> > >::type
        { };
    
        template<class _Tp>
        struct _is_member_object_pointer_impl2 :
            public false_type { };
    
        template<class _Tp, class _Cp>
        struct _is_member_object_pointer_impl2<_Tp _Cp::*> :
            public true_type { };
    
        template<class _Tp>
        struct _is_member_object_pointer_helper:
            public _and_<_is_member_object_pointer_impl1<_Tp>, _is_member_object_pointer_impl2<_Tp> >::type
        {};
    
    }
    // is_member_object_pointer
    template<class _Tp>
    struct is_member_object_pointer :
        public detail::_is_member_object_pointer_helper<typename remove_cv<_Tp>::type>::type
    { };
    

    Использованные 'и', 'или', 'не' логические операции над типами из первой части
    namespace detail
    {
        struct void_type {};
    
        //typedef void void_type;
    
        template<class _B1 = void_type, class _B2 = void_type, class _B3 = void_type, class _B4 = void_type>
        struct _or_ :
            public conditional<_B1::value, _B1, _or_<_B2, _or_<_B3, _B4> > >::type
        { };
    
    
        template<>
        struct _or_<void_type, void_type, void_type, void_type>;
    
        template<class _B1>
        struct _or_<_B1, void_type, void_type, void_type> :
            public _B1
        { };
    
        template<class _B1, class _B2>
        struct _or_<_B1, _B2, void_type, void_type> :
            public conditional<_B1::value, _B1, _B2>::type
        { };
    
        template<class _B1, class _B2, class _B3>
        struct _or_<_B1, _B2, _B3, void_type> :
            public conditional<_B1::value, _B1, _or_<_B2, _B3> >::type
        { };
    
        template<class _B1 = void_type, class _B2 = void_type, class _B3 = void_type, class _B4 = void_type>
        struct _and_;
    
    
        template<>
        struct _and_<void_type, void_type, void_type, void_type>;
    
        template<class _B1>
        struct _and_<_B1, void_type, void_type, void_type> :
            public _B1
        { };
    
        template<class _B1, class _B2>
        struct _and_<_B1, _B2, void_type, void_type> :
            public conditional<_B1::value, _B2, _B1>::type
        { };
    
        template<class _B1, class _B2, class _B3>
        struct _and_<_B1, _B2, _B3, void_type> :
            public conditional<_B1::value, _and_<_B2, _B3>, _B1>::type
        { };
    
        template<class _Pp>
        struct _not_
        {
            static const bool value = !bool(_Pp::value);
    
            typedef const bool value_type;
            typedef integral_constant<bool, _not_::value == bool(true)> type;
    
            operator value_type() const
            {	// return stored value
                return (value);
            }
    
            value_type operator()() const
            {	// return stored value
                return (value);
            }
        };
    }
    


    Здесь использованы логические операции над типами, которые с помощью шаблона conditional в итоге выбирают подходящий шаблонный тип. Шаблонное программирование во всей его красе, в итоге на этапе компиляции мы имеем уже информацию о том является ли тип членом класса. Довольно «зубодробительно», но зато как эффектно и эффективно!

    Еще немного чистого шаблонного программирования на этих же логических элементах и у нас есть is_fundamental, is_compound и т.п. признаки (меня это приводит в восторг, а вас?):

    // is_arithmetic
    template<class _Tp>
    struct is_arithmetic :
        public detail::_or_<is_integral<_Tp>, is_floating_point<_Tp> >::type
    { };
    
    // is_fundamental
    template<class _Tp>
    struct is_fundamental :
        public detail::_or_<is_arithmetic<_Tp>, is_void<_Tp>, is_null_pointer<_Tp> >::type
    {};
    
    // is_object
    template<class _Tp>
    struct is_object :
        public detail::_not_< detail::_or_< is_function<_Tp>, is_reference<_Tp>, is_void<_Tp> > >::type
    {};
    
    // is_scalar
    template<class _Tp>
    struct is_scalar :
        public detail::_or_<is_arithmetic<_Tp>, is_pointer<_Tp>, is_member_pointer<_Tp>, is_null_pointer<_Tp>/*, is_enum<_Tp>*/ >::type
    {};
    
    // is_compound
    template<class _Tp>
    struct is_compound:
        public detail::_not_<is_fundamental<_Tp> >::type
    { };
    
    Внимательный читатель заметит что определение is_enum закомментировано. Дело в том что способов отличить enum от других типов я не нашел, но считаю что это реализуемо без применения компиляторозависимых макросов. Возможно внимательный и сведущий читатель подскажет свой способ или ход мыслей на этот счет.
    Для определения того факта что тип является классом теперь необходимо всего ничего:

    namespace detail
    {
        template <class _Tp, bool _IsReference>
        struct _is_class_helper
        {
            typedef integral_constant<bool, false> type;
        };
    
        template <class _Tp>
        struct _is_class_helper<_Tp, false>
        {
            typedef integral_constant<bool,
                (is_scalar<_Tp>::value == bool(false))
                //&& !is_union<_Tp>::value >::value
                && (is_array<_Tp>::value == bool(false))
                && (is_void<_Tp>::value == bool(false))
                && (is_function<_Tp>::value == bool(false))> type;
        };
    }
    
    // is_class
    template<class _Tp>
    struct is_class :
        public detail::_is_class_helper<typename remove_cv<_Tp>::type, is_reference<_Tp>::value>::type
    { };
    

    И все бы хорошо, но union в C++ отличить от класса в общем случае не представляется возможным. Потому что они очень похожи по своим «внешним проявлениям», а отличия (к примеру невозможность наследования от union) проверить без ошибок компиляции у меня не получилось. Возможно кто-то подскажет хитрый маневр для определения union при компиляции, тогда is_class будет в точности соответствовать стандарту.

    В заключительной части данной главы я расскажу о том как был реализован std::decay и std::common_type, а так же что еще предстоит добавить в type_traits.

    Благодарю за внимание.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 15

      0
      Хотел бы уточнить две вещи:
      1. Для чего создавать доп. файлы, но без расширения, например sstream и sstream.h?
      2. Почему именно C++11, а не C++17, Там ведь добавили новые функции и т.д.?
        +1
        1. Я хотел включить заголовочные файлы с теми же именами что и в стандартной библиотеке, но опять из-за багов компиляторов все пошло не так. Некоторые компиляторы считают что если в директиву #include включается файл без расширения, но при этом у него прописан путь, то значит ему нужно «дописать» .h в конце и искать его таким образом. Видимо по той логике, что файлы без расширения могут быть только от стандартной библиотеки, а значит путей не содержат.
        Пример (файла type_traits нет в стандартной библиотеке, это заголовочный файл от моей библиотеки stdex):
        stdex расположена в «C:\my_code\stdex\», путь до type_traits будет «C:\my_code\stdex\type_traits». Тогда в компиляторе:

        include path = «C:\my_code\stdex\»
        #include <type_traits> // OK
        

        include path = «C:\my_code\»
        #include <stdex/type_traits> // compilation error "can not include 'stdex/type_traits.h': no such file or directory"
        #include <stdex/type_traits.hpp> // приходится использовать так
        

        Пришлось написать все заголовочные файлы с расширением .hpp и стандартными именами, при этом написав по сути обертки без расширения, которые просто включают в себя аналогичный файл .hpp.

        2. Потому что нужен был хотя бы C++ 11. И то не в полном объеме. Я реализовал что нужно было по минимуму, но посчитал что не дело оставлять реализацию на пол пути, да и увлекся я уже. Так что сейчас стараюсь нагнать хотя бы этот стандарт, а до последующих добраться уже имея реализацию C++ 11 будет проще намного.
        0
        Внимательный читатель уже говорил, что надо делать. В прошлый раз. :) Вот, собственно, как можно отличить enum от int'а:
        #include <iostream>
        
        struct true_type
        {
        	enum {value = 1};
        };
        
        struct false_type
        {
        	enum {value = 0};	
        };
        
        template<typename T>
        struct is_integer_impl : false_type {};
        
        #define DECLARE_INTEGER(T) \
        template<> \
        struct is_integer_impl<T> : true_type {}
        
        DECLARE_INTEGER(char);
        DECLARE_INTEGER(unsigned char);
        DECLARE_INTEGER(signed char);
        DECLARE_INTEGER(unsigned short);
        DECLARE_INTEGER(signed short);
        DECLARE_INTEGER(unsigned int);
        DECLARE_INTEGER(signed int);
        DECLARE_INTEGER(unsigned long);
        DECLARE_INTEGER(signed long);
        
        template<typename T>
        struct is_integer : is_integer_impl<T> {};
        
        template<typename T>
        struct is_class
        {
        	typedef char yes;
        	typedef int no;
        	
        	template<class U>
        	static yes doCheck(void (U::*)());
        	template<class U>
        	static no doCheck(...);
        	
        	enum {value = (sizeof(doCheck<T>(0)) == sizeof(yes))};
        };
        
        template<typename T>
        struct is_enum
        {
        	enum {value = !is_integer<T>::value && !is_class<T>::value};
        };
        
        enum TestEnum
        {
        	Item1,
        	Item2
        };
        
        struct TestStruct
        {
        	
        };
        
        int main() 
        {
        	std::cout << is_integer<int>::value << std::endl;
        	std::cout << is_enum<int>::value << std::endl;
        	std::cout << is_class<int>::value << std::endl;
        	std::cout << std::endl;
        	std::cout << is_integer<TestStruct>::value << std::endl;
        	std::cout << is_enum<TestStruct>::value << std::endl;
        	std::cout << is_class<TestStruct>::value << std::endl;
        	std::cout << std::endl;
        	std::cout << is_integer<TestEnum>::value << std::endl;
        	std::cout << is_enum<TestEnum>::value << std::endl;
        	std::cout << is_class<TestEnum>::value << std::endl;
        
        	return 0;
        }

        ideone.com/hqevaJ

        В данном случае ручное (явное) перечисление целочисленных типов играет сильно на руку — подавляет компиляторную магию.
          0
          1. Внимательный читатель заметит что is_class определен через множество проверок не просто так (и в том числе зависит от is_enum если бы он был). =)
          2.
          is_class<EnumType>::value
          будет true на некоторых компиляторах, т.к. по сути им без разницы что за контейнер. Если он не встроенный тип, то он по умолчанию считается имеющим member pointer.

          Я думаю что можно как то хитро использовать свойство enum конвертироваться в int, или отсутствие конструторов/деструкторов/функций-членов класса. Но пока что к реализации этого не дошел, тестирую.
            0
            Ну, такой вариант is_class у нас успешно работает на довольно широкой линейке компиляторов — gcc начиная с 3.9, MSVC начиная с 2008, clang и некоторая экзотика. То есть я допускаю, что существуют компиляторы, которые поведут себя в этом случае иначе, но, к счастью, давно ими не приходилось пользоваться.
              0
              Борландовские версии к сожалению не работают, сегодня проверил :) Я долго пытался состряпать workaround, но похоже пациент совсем плохо умеет SFINAE — ничего не вышло.
              Если автору важно поддерживать Borland C++, то вполне понятно почему он не применил это решение.
                0
                Нда. Как раз тот случай, когда жизнь — это боль… :( А ведь для своего времени Borland C++ был очень хорошим компилятором. Правда, не таким хорошим, как Watcom… :)
          0
          … проблема возникает с неполным типом T[] (массив без указания длины). Дело в том что данный тип не определяется некоторыми компиляторами (C++ Builder) при специализации шаблона, и универсальное решение здесь я пока что не нашел.

          Проверил код ниже на C++ Builder 6 — работает.

          namespace detail {
          
              template <typename T>
              static yes_type foo(T (*)[]);
              static no_type  foo(...);
              
              template <typename T>
              static T * declptr();
          }
          
          // is_array
          template <class Tp>
          struct is_array
          { 
              enum 
              { 
                  value = sizeof(detail::foo(detail::declptr<Tp>())) == sizeof(yes_type) 
              }; 
          };
          
          template <class Tp, std::size_t Size>
          struct is_array<Tp[Size]> 
              : true_type 
          { };
            0
            Это противоречит стандарту. Точнее теперь уже не противоречит с введением C++ 17, но до этого указатели на безразмерные массивы запрещены были. Они разрешены в чистом C, но в C++ их решили не переносить, т.к. использование их в кодовой базе на тот момент было очень ограничено, а поддержка для разработчиков компиляторов была слишком сложной.

            На SO есть как раз вопрос с отличным ответом на этот счет.

            Выдержка от туда:
            Вопрос про функцию принимающую указатель на массив неизвестной длины и почему она не компилируется g++
            int accumulate(int n, const int (*array)[])

            Ответ
            The committees decided that functions such as this, that accept a pointer or reference to an array with unknown bound, complicate declaration matching and overload resolution rules in C++. The committees agreed that, since such functions have little utility and are fairly uncommon, it would be simplest to just ban them. Hence, the C++ draft now states:
            If the type of a parameter includes a type of the form pointer to array of unknown bound of T or reference to array of unknown bound of T, the program is ill-formed.
              0
              Таки шашечки или ехать? :)
              В бусте тоже встречаются workaround`ы с нестандартными особенностями, обложенные проверками под конкретные компиляторы.
              Ведь если мы работаем в условиях такой плохой поддержки стандарта, то сетовать на несоответствие стандарту некоторым образом лукавство, особенно, если код обеспечивает требуемое поведение.
              Поэтому тут надо выбирать что важнее.

              PS. Да, и я в курсе этих особенностей, т.к. сам некоторым образом решал подобную вашей задачу (пример: habr.com/post/277727), но для линейки старых GCC и Intel под Linux и BSD. Попробуйте вашу реализацию на GCC 2.x, возможно найдется множество интересных локальных задачек по нахождению обходных путей.
                0
                Довольно иронично то, что именно на этот вопрос я и отвечал в предыдущей статье про nullptr =)

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

                P.S.: я именно вашу статью тоже с удовольствием давно уже прочел и плюс поставил. Это действительно очень занимательно, но у меня про другое немного.
                  +1
                  Сама статья про другое, но написана она была по мотивам создания практически такого же набора инструментов, как у вас. Платформа только различается и целевые компиляторы.
                  Вы не подумайте, мне очень близка ваша идея строгого соответствия «букве» закона. Но я, как практик, при столкновении с подобными трудностями в своей реализации, делая выбор между «поддерживать фичу с оговорками» или «не поддерживать вовсе» выбираю первое :)

                  Взять, например, задачу определения наличия функции-члена класса с заданной сигнатурой, которая нерешаема в рамках стандартного С++98. Однако, эта возможность была необходима в инструментарии, который я создавал. Без нее было бы слишком неудобно им пользоваться. Поэтому я пошел на этот компромисс.

                  Также приходилось сталкиваться в ситуацией, когда невозможно написать общий код для всей линейки нужных компиляторов, и таки приходилось делать условную компиляцию. Т.к. баги-фичи одних компиляторов были взаимоисключающими относительно других. Общий знаменатель был невозможен.
            0
            Кстати, очень интересно было бы посмотреть на вашу реализацию common_type.

            Могу сказать, что в моем случае — это как раз один из тех примеров, когда пришлось делать условную компиляцию. Для GCC 2.х реализация оказалась настолько нетривиальна, что брать ее как основную мне не позволила совесть.
              0
              Посмотреть можете в репозитории же. А вот со статьей пока все откладываю, никак время не выкроить.

              Кстати is_array как и is_enum я реализовал уже в полном объеме.
              –1
              imho. А почему бы не назвать namespace stdx (по антологии xhtml (extertion html))? Так было бы, на мой взгляд, красивше, короче, да и почему и нет?

              P.S. Чисто, ради интереса

              Only users with full accounts can post comments. Log in, please.