TypeList и Крестики-нолики

    Захотелось, наконец-то(!), попробовать variadic templates, так как до сих пор привязан к 10й студии, где ничего этого нету. А чтобы долго не думать, где же можно бесполезно использовать variadic templates, пришла идея попробовать, как будет выглядеть Typelist. Для тех, кто ещё не знает, что это такое, я постараюсь объяснять по ходу дела, а тем, кому это скучно — может сразу пролистать вниз — попробуем написать подобие крестиков-ноликов с использованием Typelist.
    Итак, TypeList:

    TypeList
    namespace internal {
    struct Void
    {
    };
    } // internal
    
    template<typename ...Args>
    struct TypeList
    {
        typedef internal::Void Head;
        typedef internal::Void Tail;
    };
    
    typedef TypeList<> EmptyTypeList;
    
    template<typename H, typename ...T>
    struct TypeList<H, T...>
    {
        typedef H Head;
        typedef TypeList<T...> Tail;
    };
    


    Типичный TypeList представляет собой «голову»(Head) и «хвост»(Tail), который в свою очередь также является списком типов. Использование:
    typedef TypeList<float, double, long double> floating_point_types;
    

    Раньше, без С++11, это выглядело так:
    Старый TypeList
    template <class H, class T>
    struct typelist
    {
        typedef H head;
        typedef T tail;
    };
    
    typedef typelist<float, typelist<double, long double> > floating_point_types;
    

    И макросы в помощь:
    #define TYPELIST_1(T1) typelist<T1, null_typelist>
    #define TYPELIST_2(T1, T2) typelist<T1, TYPELIST_1(T2) >
    #define TYPELIST_3(T1, T2, T3) typelist<T1, TYPELIST_2(T2, T3) >
    ...
    #define TYPELIST_50...
    


    Но теперь, благодаря variadic templates, можно избавиться и от макросов, и от ограничения на количество типов в списке.
    Собственно говоря интересным является то, как работать со списком типов, как определить операции над ним и что это даёт в конечном итоге(кому интересно более детальное описание и кто ещё не видел Modern C++ Design — советую почитать — не важно, что это 2001 год!).
    Итак, как видно, я определил вспомогательный тип internal::Void, который будет работать, как сигнальный флажок и говорить, что список типов пуст(как минимум, для случая, когда пользователь не указал ничего: TypeList<>, или, когда со списка удалено все элементы). Начнём с начала:

    IsEmpty


    IsEmpty
    template<typename TL>
    struct IsEmpty :
        std::true_type
    {
    };
    
    template<>
    struct IsEmpty<TypeList<internal::Void, internal::Void>> :
        std::true_type
    {
    };
    
    template<typename ...Args>
    struct IsEmpty<TypeList<Args...>> :
        std::integral_constant<bool,
            std::is_same<typename TypeList<Args...>::Head, internal::Void>::value &&
            IsEmpty<typename TypeList<Args...>::Tail>::value>
    {
    };
    


    Здесь видно почти всё, что нам нужно, для определения других операций. Как видно, сначала мы определяем «костяк»: тип IsEmpty параметризован одним типом. По сути, это «функция», принимающая один аргумент. Поскольку тип TL означает — «любой тип», мы делаем полную специализацию шаблона для случая с пустым списком: TypeList<internal::Void, internal::Void>(можно было бы и просто TypeList<> или, как раз для этого, я определил тип EmptyTypeList) и частичную специализацию, которая работает — «для любого списка типов». Таким образом, наша «функция» определена только для списка типов. В новом стандарте появились такие удобные штуки, как std::integral_constant, которые очень сильно упрощают жизнь: в случае с struct IsEmpty : std::true_type, IsEmpty имеет член класса value, ряд typedef-ов и оператор преобразования в bool.
    Как это использовать ?:
    typedef TypeList<int> TL1;
    std::cout << std::boolalpha << IsEmpty<TL1>::value << " " << IsEmpty<EmptyTypeList>() << std::endl;
    

    Пустой ли список мы имеем определяет следующее выражение:
    std::is_same<typename TypeList<Args...>::Head, internal::Void>::value &&
    IsEmpty<typename TypeList<Args...>::Tail>::value
    

    дословно — «список пуст, если его голова — это вспомогательный тип, обозначающий void И если его хвост также является пустым списком». Как видно, здесь используется рекурсия, которую, как раз и останавливает, полная специализация шаблона для пустого списка.
    Дальше:

    Contains


    Contains
    template<typename T, typename TL>
    struct Contains :
        std::false_type
    {
    };
    
    template<typename ...Args>
    struct Contains<internal::Void, Args...> :
        std::false_type
    {
    };
    
    template<typename T, typename ...Args>
    struct Contains<T, TypeList<Args...>> :
        std::integral_constant<bool,
            std::is_same<typename TypeList<Args...>::Head, T>::value ||
            Contains<T, typename TypeList<Args...>::Tail>::value
            >
    {
    };
    


    Contains определяет есть ли указанный тип T внутри списка типов TL. Использование:

    Использование:
    typedef TypeList<double, float, float, double, int, char, char, int, char> TL;
    std::cout << std::boolalpha << Contains<char, TL>::value << " " << Contains<float, TypeList<double>>() << std::endl;
    

    Снова же: «если голова списка это наш тип T, то T есть внутри списка, а иначе — посмотреть, есть ли T в хвосте списка».
    Частичная специализация — мере предосторожности — а вдруг кто-то воспользуется нашим типом internal::Void?

    Length


    Length
    template<typename TL>
    struct Length :
        std::integral_constant<unsigned int, 0>
    {
    };
    
    template<typename ...Args>
    struct Length<TypeList<Args...>> :
        std::integral_constant<unsigned int,
            IsEmpty<TypeList<Args...>>::value
                ? 0
                : 1 + Length<typename TypeList<Args...>::Tail>::value>
    {
    };
    


    Если список пуст — длина нулевая, а иначе — это единица(потому что присутствует «голова»(Head)) + длина хвоста:
    typedef TypeList<double, float, float, double, int, char, char, int, char> TL;
    std::cout << Length<TL>::value << " " << Length<EmptyTypeList>() << std::endl;
    

    TypeAt


    template<unsigned int N, typename TL>
    struct TypeAt
    {
        typedef internal::Void type;
    };
    

    — возвращает тип по индексу, почти, как массив. Реализация — первый заход(меняем тип N на int):
    //template<int N, typename ...Args>
    //struct TypeAt<N, TypeList<Args...>>
    //{    
    //    typedef typename std::conditional<N == 0,
    //        typename TypeList<Args...>::Head,
    //        typename TypeAt<N - 1, typename TypeList<Args...>::Tail>::type>::type type;
    //};
    

    — всё будет прекрасно работать, но! — хотелось бы быть предупреждённым, если указан слишком большой индекс. Можно бы было выкрутиться и с текущей реализацией, но здесь нужно учитывать то, что шаблон должен быть корректно инстанцирован для случая N=-1. Поэтому идём другим путём:
    template<typename ...Args>
    struct TypeAt<0, TypeList<Args...>>
    {
        typedef typename TypeList<Args...>::Head type;
    };
    
    template<unsigned int N, typename ...Args>
    struct TypeAt<N, TypeList<Args...>>
    {
        static_assert(N < Length<TypeList<Args...>>::value, "N is too big");
        
        typedef typename TypeAt<N - 1, typename TypeList<Args...>::Tail>::type type;
    };
    

    — голова имеет нулевой индекс, а для других случаев — будем одновременно уменьшать индекс на единицу и «съедать» кусочек хвоста(передвигаемся слева на право), пока не сможем отнять — индекс нулевой, а текущая голова и есть нужный нам тип! Использование:
    typedef TypeList<char, short> TL2;
    static_assert(std::is_same<TypeAt<1, TL2>::type, short>::value, "Something wrong!");
    

    Вывод списка


    operator<<
    // Пустой список
    std::ostream& operator<<(std::ostream& ostr, EmptyTypeList)
    {
    	ostr << "{}";
    	return ostr;
    }
    
    template<typename TL>
    void PrintTypeListHelper(TL, std::ostream& ostr)
    {
    }
    
    template<typename T>
    void PrintTypeListHead(T, std::ostream& ostr)
    {
    	ostr << typeid(T).name();
    }
    
    template<typename ...Args>
    void PrintTypeListHead(TypeList<Args...> tl, std::ostream& ostr)
    {
    	ostr << tl;
    }
    
    template<typename Head, typename ...Args>
    void PrintTypeListHelper(TypeList<Head, Args...>, std::ostream& ostr)
    {
    	PrintTypeListHead(Head(), ostr);
    	if(!IsEmpty<TypeList<Args...>>::value)
    	{
    		ostr << ' ';
    		PrintTypeListHelper<Args...>(TypeList<Args...>(), ostr);
    	}
    }
    
    template<typename ...Args>
    std::ostream& operator<<(std::ostream& ostr, TypeList<Args...> tl)
    {
    	ostr << '{';
    	PrintTypeListHelper(tl, ostr);
    	ostr << '}';
    	return ostr;
    }
    


    Эти функции помогают аккуратно вывести обычные списки типов и вложенные, например:
    typedef TypeList<double, float, float, double, int, char, char, int, char> TL;
    std::cout << TL() << std::endl;
    
    typedef TypeList<TL2, double, TL2> TL10;
    std::cout << TL10() << std::endl;
    

    {double float float double int char char int char}

    {{char short} double {char short}}


    Append и Add


    Append, Add
    Функции добавления в конец списка, с маленькой разницей:
    
    template<typename TOrTL2, typename TL>
    struct Append
    {
    };
    
    template<typename T, typename ...Args>
    struct Append<T, TypeList<Args...>>
    {
        typedef TypeList<Args..., T> type;
    };
    
    template<typename ...Args1, typename ...Args2>
    struct Append<TypeList<Args1...>, TypeList<Args2...>>
    {
        typedef TypeList<Args2..., Args1...> type;
    };
    
    template<typename T, typename TL>
    struct Add
    {
    };
    
    template<typename T, typename ...Args>
    struct Add<T, TypeList<Args...>>
    {
    	typedef TypeList<Args..., T> type;
    };
    


    При использовании Append со списком типов в первом аргументе происходит «разложение» на составные. Т.е.:
    typedef TypeList<int> TL1;
    typedef TypeList<char, short> TL2;
    
    std::cout << TL1() << ", " << TL2() << std::endl;
    std::cout << Add<TL2, TL1>::type() << ", " << Append<TL2, TL1>::type() << std::endl;
    

    {int}, {char short}
    {int {char short}}, {int char short}
    В первом случае длина результата — 2, тогда как во втором — 3, так как добавляемый список типов «разложился» на компоненты.

    RemoveAll


    Удаление элементов
    template<typename TOrTL2, typename TL>
    struct RemoveAll
    {
    };
    
    template<typename T, typename ...Args>
    struct RemoveAll<T, TypeList<Args...>>
    {
    private:
        typedef typename RemoveAll<T, typename TypeList<Args...>::Tail>::type Removed;
        typedef typename TypeList<Args...>::Head Head;
        
    public:
        typedef typename std::conditional<
            std::is_same<Head, T>::value,
            Removed,
            typename Append<Removed, TypeList<Head>>::type
            >::type type;
    };
    
    template<typename T, typename Head>
    struct RemoveAll<T, TypeList<Head>>
    {
        typedef typename std::conditional<
            std::is_same<Head, T>::value,
            EmptyTypeList,
            TypeList<Head>>::type type;
    };
    
    template<typename T>
    struct RemoveAll<T, EmptyTypeList>
    {
        typedef EmptyTypeList type;
    };
    


    Удаление делается так:
    • С пустого списка мы ничего не можем удалить
    • Если у нас список с одним элементом(только голова) — то вернуть пустой список, если тип головы совпадает с заданным или ничего не изменять в противном случае
    • Для всех остальных случаев — удалить элементы с хвоста и если тип головы не совпадает с заданным типом — добавить её до результата удаления

    Важно то, что, поскольку при удалении с хвоста мы сгрупировали результат в другой список типов, при объединении, используется Append, который «раскручивает» назад сгруппированный список типов.
    Использование:
    typedef TypeList<double, float, float, double, int, char, char, int, char> TL;
    std::cout << TL() << std::endl;
    std::cout << RemoveAll<char, TL>::type() << std::endl;
    

    {double float float double int char char int char}
    {double float float double int int}

    Можно написать ещё одну версию RemoveAll, которая будет удалять со второго списка типов все те, которые есть в первом. Но! В таком случае ее нельзя использовать для списков, которые содержат другие списки:

    RemoveAll v2
    //template<typename Head2, typename ...Args1>
    //struct RemoveAll<TypeList<Head2>, TypeList<Args1...>>
    //{
    //    typedef typename RemoveAll<Head2, TypeList<Args1...>>::type type;
    //};
    //
    //template<typename ...Args1>
    //struct RemoveAll<EmptyTypeList, TypeList<Args1...>>
    //{
    //    typedef TypeList<Args1...> type;
    //};
    //
    //template<typename ...Args2, typename ...Args1>
    //struct RemoveAll<TypeList<Args2...>, TypeList<Args1...>>
    //{
    //private:
    //    typedef TypeList<Args2...> TL2;
    //    typedef TypeList<Args1...> TL1;
    //    
    //    typedef typename RemoveAll<typename TL2::Tail, TL1>::type Removed;
    //    typedef typename TL2::Head Head2;
    //    
    //public:
    //    typedef typename std::conditional<
    //        Contains<Head2, Removed>::value,
    //        typename RemoveAll<Head2, Removed>::type,
    //        TL1
    //        >::type type;    
    //};
    


    Пример:
    typedef TypeList<double, float, float, double, int, char, char, int, char> TL;
    typedef TypeList<char, double> TL2;
    std::cout << TL() << std::endl;
    std::cout << RemoveAll<TL2, TL>::type() << std::endl;
    

    {double float float double int char char int char}
    {float float int int}

    RemoveDuplicates


    RemoveDuplicates
    template<typename TL>
    struct RemoveDuplicates
    {
    };
    
    template<>
    struct RemoveDuplicates<EmptyTypeList>
    {
        typedef EmptyTypeList type;
    };
    
    template<typename ...Args>
    struct RemoveDuplicates<TypeList<Args...>>
    {
    private:
        typedef TypeList<Args...> TL;
        typedef typename RemoveAll<typename TL::Head, typename TL::Tail>::type HeadRemovedFromTail;
        typedef typename RemoveDuplicates<HeadRemovedFromTail>::type TailWithoutDuplicates;
    public:
        typedef typename Append<TailWithoutDuplicates, TypeList<typename TL::Head>>::type type;
    };
    


    Функция, которая удаляет дубликаты:
    • С пустого списка мы ничего не можем удалить
    • Удаляем такие же элементы, как и голова из хвоста
    • Рекурсивно вызываем функцию для хвоста
    • Объединяем голову с результатом

    Пример:
    typedef TypeList<double, float, float, double, int, char, char, int, char> TL;
    std::cout << TL() << std::endl;
    std::cout << RemoveDuplicates<TL>::type() << std::endl;
    

    {double float float double int char char int char}
    {double float int char}

    Find


    Позиция типа в списке
    struct Constants
    {
        typedef std::integral_constant<unsigned int, UINT_MAX> npos;
    };
    
    namespace internal {
    template<typename T, unsigned int IndexFrom, typename TL>
    struct FindHelper :
        std::integral_constant<unsigned int, 0>
    {
    };
    
    template<typename T, unsigned int IndexFrom>
    struct FindHelper<T, IndexFrom, EmptyTypeList> :
        std::integral_constant<unsigned int, 0>
    {
    };
    
    template<typename T, unsigned int IndexFrom, typename ...Args>
    struct FindHelper<T, IndexFrom, TypeList<Args...>> :
        std::integral_constant<unsigned int,
            std::is_same<typename TypeList<Args...>::Head, T>::value
            ? IndexFrom
            : IndexFrom + 1 + FindHelper<T, IndexFrom, typename TypeList<Args...>::Tail>::value>
    {
    };
    } // internal
    
    template<typename T, typename TL>
    struct Find
    {
    };
    
    template<typename T>
    struct Find<T, EmptyTypeList> :
        Constants::npos
    {
    };
    
    template<typename ...Args>
    struct Find<internal::Void, TypeList<Args...>> :
        Constants::npos
    {
    };
    
    template<typename T, typename ...Args>
    struct Find<T, TypeList<Args...>> :
        std::integral_constant<unsigned int,
            Contains<T, TypeList<Args...>>::value
            ? internal::FindHelper<T, 0, TypeList<Args...>>::value
            : Constants::npos::value>
    {
    };
    



    Несколько вещей:
    Constants — для констант. В нашем случае только для константы, которая говорит о том, что элемент не найден(constexp не поддерживается в моей студии, поэтому UINT_MAX)
    internal::FindHelper — собственно говоря, «штука», которая ищет тип в списке, который точно(!) этот тип содержит(дополнительный параметр IndexFrom — начальное значение отсчёта, совсем не нужная вещь:) — рассчитана на случай, когда нужно будет задавать с какой позиции начинать поиск)

    Снова же — ничего замысловатого — если указанный тип и тип головы списка совпадает — тогда индекс — нулевой, а иначе — переместится вправо на 1цу и сделать то же самое для хвоста списка.
    Пример:
    typedef TypeList<double, float, float, double, int, char, char, int, char> TL;
    std::cout << std::boolalpha << std::is_same<TypeAt<Find<double, TL>::value, TL>::type, double>() << std::endl;
    


    Slice


    Slice
    namespace internal {
    template<unsigned int IndexBegin, unsigned int IndexEnd, typename TL>
    struct SliceHelper
    {
    };
    
    template<unsigned int IndexBegin, unsigned int IndexEnd>
    struct SliceHelper<IndexBegin, IndexEnd, EmptyTypeList>
    {
        typedef EmptyTypeList type;
    };
    
    template<unsigned int IndexBegin, typename ...Args>
    struct SliceHelper<IndexBegin, IndexBegin, TypeList<Args...>>
    {
        typedef TypeList<typename TypeAt<IndexBegin, TypeList<Args...>>::type> type;
    };
    
    template<unsigned int IndexBegin, unsigned int IndexEnd, typename ...Args>
    struct SliceHelper<IndexBegin, IndexEnd, TypeList<Args...>>
    {
    private:
        static_assert(IndexEnd >= IndexBegin, "Invalid range");
        typedef TypeList<Args...> TL;
    public:
        typedef typename Add<
            typename TypeAt<IndexEnd, TL>::type,
            typename SliceHelper<IndexBegin, IndexEnd - 1, TL>::type
            >::type type;
    };
    
    } // internal
    
    template<unsigned int IndexBegin, unsigned int IndexAfterEnd, typename TL>
    struct Slice
    {
    };
    
    template<unsigned int IndexBegin, unsigned int IndexEnd, typename ...Args>
    struct Slice<IndexBegin, IndexEnd, TypeList<Args...>>
    {
        typedef typename internal::SliceHelper<IndexBegin, IndexEnd, TypeList<Args...>>::type type;
    };
    
    template<unsigned int Index, typename TL>
    struct CutTo
    {
    };
    
    template<unsigned int Index, typename ...Args>
    struct CutTo<Index, TypeList<Args...>>
    {
        typedef typename Slice<0, Index, TypeList<Args...>>::type type;
    };
    
    template<unsigned int Index, typename TL>
    struct CutFrom
    {
    };
    
    template<unsigned int Index, typename ...Args>
    struct CutFrom<Index, TypeList<Args...>>
    {
    private:
        typedef TypeList<Args...> TL;
    public:
        typedef typename Slice<Index, Length<TL>::value - 1, TL>::type type;
    };
    


    «Вырезает» указанную часть списка:
    • С пустого списка мы ничего не можем взять
    • Когда указанные начало(IndexBegin) и конец(IndexEnd) совпадают, то это аналогично операции TypeAt
      Начиная с конца указанного диапазона, взять элемент и добавить к результату рекурсивного вызова(в котором конец указанного диапазона уменьшается на 1цу)


    • Спасибо за внимание!
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 14

      +2
      Превращать вариадики в списки типов, мсье знает толк. Но у меня всё же вопрос: Зачем?
      С вариадиками можно работать в нативном виде, без преобразования к рудиментарному списку типов. Хранить их не сложно, сделать псевдоконтейнер и всё.

      template<typename… Args>
      struct holder;

      Этого достаточно для того чтобы работать как угодно с вариадиками.

      Небольшой пример ideone.com/tb6Jm7 возможно натолкнет на дальнейшее развитие знаний.
        0
        Да, конечно. Ответ: just for fun. Тут всё можно заменить на что-то вроде:
        template<typename T, typename ...Args>
        struct Exists :
            std::false_type
        {
        };
        
        template<typename T, typename ...Args>
        struct Exists<T, T, Args...> :
            std::true_type
        {
        };
        
        template<typename T, typename NotT, typename ...Args>
        struct Exists<T, NotT, Args...> :
            std::integral_constant<bool, Exists<T, Args...>::value>
        {
        };
        

        и т.д.
        Спасибо за пример
        +1
        Вопрос 1:
        А как по этому TypeList'у foreach'eм пройтись так чтоб без for'a? :)
        Скажем в ваш TypeList ложим список классов со статическими функциями. А потом вызываем некую статическую do_func() из них.

        Вопрос 2:
        И вообще — можно ли аналогом такого вот TypeList'а организовать систему с плагинами? Ну, есть список объектов классов в каждом классе есть, скажем функция (не статичексая) begin() и end(). Нужно в определённых местах вызвать все begin() и все end() из списка. И делать это в критических по времени местах. Для этого не использовать vector и for, и естественно классы не содержат виртуальных функций. Так чтобы компилятор преобразовал это просто в вызов функций по порядку.

        P.S. Признаться по правде не силен в темплейтах.
          +1
          Вопрос 1:
          А как по этому TypeList'у foreach'eм пройтись так чтоб без for'a? :)
          Скажем в ваш TypeList ложим список классов со статическими функциями. А потом вызываем некую статическую do_func() из них.
          Например, так:
          Скрытый текст
          template <class First, class Rest>
          struct typelist;
          struct nil;
          
          // ----------------------------------------------------------------------------
          
          template <class TypeCollection, template <class T> class StaticFunctor>
          struct ForEach;
          
          template <class Last, template <class T> class StaticFunctor>
          struct ForEach<typelist<Last, nil>, StaticFunctor>
          {
              static void iterate()
              {
                  StaticFunctor<Last>::call();
              }
          };
          
          template <class First, class Rest, template <class T> class StaticFunctor>
          struct ForEach<typelist<First, Rest>, StaticFunctor>
          {
              static void iterate()
              {
                  StaticFunctor<First>::call();
                  ForEach<Rest, StaticFunctor>::iterate();
              }
          };
          
          #define FOR_EACH(collection, action)                \
            ForEach<collection, action>::iterate()
          
          #define DEFINE_STATIC_CALLER_FUNCTOR(function)      \
            namespace Call                                    \
            {                                                 \
                template <class T>                            \
                struct function                               \
                {                                             \
                    static void call()                        \
                    {                                         \
                        T::function();                        \
                    }                                         \
                };                                            \
            }
          
          // ----------------------------------------------------------------------------
          
          #include <iostream>
          
          #define DEFINE_CLASS_SPECIMEN(name)                 \
            struct name                                       \
            {                                                 \
                static                                        \
                void do_function()                            \
                {                                             \
                    std::cout << #name "::function()\n";      \
                }                                             \
            };
          
          DEFINE_CLASS_SPECIMEN(A)
          DEFINE_CLASS_SPECIMEN(B)
          DEFINE_CLASS_SPECIMEN(C)
          
          typedef typelist<A, typelist<B, typelist<C, nil> > > TestTypelist;
          
          DEFINE_STATIC_CALLER_FUNCTOR(do_function)
          
          int main()
          {
              FOR_EACH(TestTypelist, Call::do_function);
          }
          

          (В Бусте наверняка есть что-то готовое для этого.)
            +1
            Вопрос 2:
            ...

            Как-то я рисовал систему с плагинами и как раз определенные методы искал у классов, делая некоторые постконструкторы и преддеструкторы, вызов которых был аналогичен конструкторам и деструкторам (порядок вызова их в иерархии наследования), а для определения метода в классе хорошо подходит SFINAE. (По приведенной ссылке можно это посмотреть на реализациях поиска и вызова FinalizeConstruct / BeforeRelease).
              0
              А я надеялся что там не о том. Эх теперь придется читать :))
              Вам виртуальный +1
                +3
                Да, кстати, может поздно и не важно, но статья действительно хорошая(как и реализация) и у меня возник вопрос ещё тогда по поводу FinalizeConstruct /BeforeRelease. Может, это смешно, но всё таки спрошу — почему не сделать что-то типа такого макроса для проверки есть ли метод в классе ?:
                #define DEFINE_METHOD_CHECKER(RETURN_TYPE, METHOD_NAME, PARAMETERS)     \
                template<typename T>                                                    \
                struct Is ## METHOD_NAME ## MemberFunctionExists                        \
                {                                                                       \
                private:                                                                \
                    typedef char True;                                                  \
                    typedef char (&False)[2];                                           \
                    template<typename U, RETURN_TYPE (U::*)PARAMETERS = &U::METHOD_NAME>\
                    struct Checker                                                      \
                    {                                                                   \
                        typedef True Type;                                              \
                    };                                                                  \
                    template<typename U>                                                \
                    static typename Checker<U>::Type Tester(const U*);                  \
                    static False Tester(...);                                           \
                public:                                                                 \
                    enum { value = (sizeof(Tester(static_cast<const T*>(0))) == sizeof(True)) }; \
                }
                
                // IsMethodMemberFunctionExists<T>::value
                DEFINE_METHOD_CHECKER(int, Method, (bool));
                // IsTestMemberFunctionExists<T>::value
                DEFINE_METHOD_CHECKER(int*, Test, (int&, char));
                
                #include <iostream>
                
                class Exists
                {
                public:
                    int Method(bool);
                    int* Test(int&, char);
                    
                };
                
                class NotExists
                {
                };
                
                int main()
                {
                    std::cout << IsMethodMemberFunctionExists<Exists>::value << std::endl;
                    std::cout << IsTestMemberFunctionExists<Exists>::value << std::endl;
                
                    std::cout << IsMethodMemberFunctionExists<NotExists>::value << std::endl;
                    std::cout << IsTestMemberFunctionExists<NotExists>::value << std::endl;
                }
                
                  0
                  Пределу совершенствования кода как правило нет :)
                  Да, такой макрос вполне мог существовать в рамках приведенного материала. Для меня макросы всеже менее приемлемы в коде, чем их законные языковые заменители. А так как в реализации не было много мест, где надо проверять наличие тех или иных методов класса и такая проверка не позиционировалась как вспомогательная для кода пользователя, то я просто написал пару почти аналогичных проверок, без заворачивания их в макросы и обобщения на все случаи. (В приведенных Вами реализациях еще бы const для методов поддержать… Это, думаю, не проблема:) А там еще вспоминается про volatile. Получается хорошее обобщение.) В моем случае я не стал просто делать такие усилия. Не скажу, что я долго задумывался над выбором простого почти «копипастного» небольшого решения или полноценного обобщения. Как правило к макросам стараюсь прибегнуть как можно реже, когда их выгода сильно перекрывает их недостатки или вообще их существование. В то же время полностью не чураюсь их, и если без них никак не достичь желаемого результата, то «пачкаюсь ими по полной» и осознанно, как пример тому еще один мой пост. Где без макросов достичь желаемого минимализма у меня никак не получилось.
                    +1
                    Спасибо за ответ.
                    А по поводу const, volatile:

                    DEFINE_METHOD_CHECKER(int, Method, (bool) const);
                    DEFINE_METHOD_CHECKER(int*, Test, (int&, char) volatile);
                    

                    Т.е. Вы просто указываете полную сигнатуру метода + возвращаемый тип. Удобно в том плане, что различие между этим и действительным объявлением метода в классе — только 2 запятые.

                    Кстати, я пошёл дальше и ради интереса — а можно ли узнать о методе, если он шаблонный?
                    #define DEFINE_TEMPLATE_METHOD_CHECKER(RETURN_TYPE, METHOD_NAME, PARAMETERS_INST, ARGS) \
                    template<typename T>                                                    \
                    struct Is ## METHOD_NAME ## TemplateMemberFunctionExists				\
                    {                                                                       \
                    private:                                                                \
                    	typedef char True;                                                  \
                    	typedef char (&False)[2];                                           \
                    	template<typename U, RETURN_TYPE (U::*)PARAMETERS_INST = &U::template METHOD_NAME ARGS >\
                    	struct Checker														\
                    	{                                                                   \
                    		typedef True Type;												\
                    	};                                                                  \
                    	template<typename U>                                                \
                    	static typename Checker<U>::Type Tester(const U*);                  \
                    	static False Tester(...);                                           \
                    public:                                                                 \
                    	enum { value = (sizeof(Tester(static_cast<const T*>(0))) == sizeof(True)) }; \
                    }
                    
                    // IsVCFailTemplateMemberFunctionExists<T>::value
                    DEFINE_TEMPLATE_METHOD_CHECKER(void, VCFail, (), <int>);
                    
                    // IsVCOkTemplateMemberFunctionExists<T>::value
                    DEFINE_TEMPLATE_METHOD_CHECKER(void, VCOk, (int, bool), <int>);
                    
                    #include <iostream>
                    
                    class Exists
                    {
                    public:
                    	template<typename T>
                    	void VCFail();
                    
                    	template<typename T>
                    	void VCOk(T, bool);
                    
                    };
                    
                    class NotExists
                    {
                    };
                    
                    int main()
                    {
                    	std::cout << IsVCOkTemplateMemberFunctionExists<Exists>::value << std::endl;
                    	std::cout << IsVCOkTemplateMemberFunctionExists<NotExists>::value << std::endl;
                    
                    	//std::cout << IsVCFailTemplateMemberFunctionExists<Exists>::value << std::endl;
                    	std::cout << IsVCFailTemplateMemberFunctionExists<NotExists>::value << std::endl;
                    }
                    

                    И, всё работает, только не со студией:) 3й рядок не компилируется с ошибкой «An internal error has occurred in the compiler».
                    vc++(13 студия, Microsoft ® C/C++ Optimizing Compiler Version 18.00.21005.1 for x86): результат;
                    gcc(g++ 4.8.1 (g++ -Wall -std=c++11 -O2)): результат;
                    clang(clang 3.4 (clang++ -Wall -std=c++11 -O2)): результат;

                    На всякий случай — зарепортил
                      0
                      Да, забыл добавить, что с 10й студией — компилируется, но выдаёт неправильный результат(0 вместо 1)
                    0
                    Кстати, спасибо за наведение на мысль. Учту в дальнейшем рефакторинге. Приведенный материал по линк я все же по небольшими порциями, но развиваю…
                  +3
                  Попроще, чем у ilammy. Можно сделать так. Основной костяк:
                  // Делается по "накатанной" схеме - определяем основу шаблона.
                  // В этом случае шаблон инстанцируется для
                  // - любого другого шаблонного класса, который имеет один параметр(<b>Caller</b>)
                  // - любого другого типа(<b>TL</b>)
                  template<template<typename> class Caller, typename TL>
                  struct ForEach
                  {
                  };
                  
                  // Дальше - уточняем - на самом деле мы хотим иметь только:
                  // - любой другой шаблонный класса, как первый аргумент(<b>Caller</b>)
                  // - именно какую-то версию <b>TypeList</b>, как другой аргумент
                  template<template<typename> class Caller, typename ...Args>
                  struct ForEach<Caller, TypeList<Args...>>
                  {
                  	typedef TypeList<Args...> TL;
                  
                  	void operator()() const
                  	{
                                     // Для Caller<T> - должен быть перегружен operator(),
                                     // где T - это TL::Head. Можно выбрать и любую другую ф-ю.
                  		Caller<typename TL::Head>()();
                  
                                    // Рекурсия - вызываем всё то же для хвоста
                  		ForEach<Caller, typename TL::Tail>()();
                  	}
                  };
                  
                  // Говорим, что когда список типов пуст - ничего не делать
                  template<template<typename> class Caller>
                  struct ForEach<Caller, EmptyTypeList>
                  {
                  	void operator()() const
                  	{
                  	}
                  };
                  
                  


                  Всё, теперь определяем наш «функтор» — по сути тело цикла для каждого типа T:
                  // Для любого типа
                  template<typename T>
                  struct Call
                  {
                  	void operator()() const
                  	{
                  		std::cout << typeid(T).name() << std::endl;
                  	}
                  };
                  
                  // Делаем полную специализацию шаблона для 'float', допустим
                  template<>
                  struct Call<float>
                  {
                  	void operator()() const
                  	{
                  		std::cout << "Call<float>" << std::endl;
                  	}
                  };
                  


                  Используем:
                  typedef TypeList<double, float, float, double, int, char, char, int, char> TL;
                  ForEach<Call, TL>()();
                  


                  Результат:
                  double
                  Call<float>
                  Call<float>
                  double
                  int
                  char
                  char
                  int
                  char
                  
                    0
                    Круть. Компилит ток гад, долго.
                    Эхх накрутили они в С++11 с этими темплейтами, лучше б нормально это все через прекомпилятор/макросы реализовали.
                  0
                  Компилирование рекурсивных алгоритмов при большом размере контейнера будет зависеть от установленной максимальной глубины рекурсии. К примеру, у меня clang-700-1.81 и по дефолту максимальная глубина равна 256. Соотвественно, при размере контейнера примерно >100 лимит рекурсии превышается (все это я, естественно, профайлил). Конечно, можно увеличить глубину, но размер контейнера все равно будет ограничен возможностями компилятора и ОС.

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