Pull to refresh

Comments 14

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

template<typename… Args>
struct holder;

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

Небольшой пример ideone.com/tb6Jm7 возможно натолкнет на дальнейшее развитие знаний.
Да, конечно. Ответ: 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:
А как по этому TypeList'у foreach'eм пройтись так чтоб без for'a? :)
Скажем в ваш TypeList ложим список классов со статическими функциями. А потом вызываем некую статическую do_func() из них.

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

P.S. Признаться по правде не силен в темплейтах.
Вопрос 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);
}

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

Как-то я рисовал систему с плагинами и как раз определенные методы искал у классов, делая некоторые постконструкторы и преддеструкторы, вызов которых был аналогичен конструкторам и деструкторам (порядок вызова их в иерархии наследования), а для определения метода в классе хорошо подходит SFINAE. (По приведенной ссылке можно это посмотреть на реализациях поиска и вызова FinalizeConstruct / BeforeRelease).
А я надеялся что там не о том. Эх теперь придется читать :))
Вам виртуальный +1
Да, кстати, может поздно и не важно, но статья действительно хорошая(как и реализация) и у меня возник вопрос ещё тогда по поводу 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;
}
Пределу совершенствования кода как правило нет :)
Да, такой макрос вполне мог существовать в рамках приведенного материала. Для меня макросы всеже менее приемлемы в коде, чем их законные языковые заменители. А так как в реализации не было много мест, где надо проверять наличие тех или иных методов класса и такая проверка не позиционировалась как вспомогательная для кода пользователя, то я просто написал пару почти аналогичных проверок, без заворачивания их в макросы и обобщения на все случаи. (В приведенных Вами реализациях еще бы const для методов поддержать… Это, думаю, не проблема:) А там еще вспоминается про volatile. Получается хорошее обобщение.) В моем случае я не стал просто делать такие усилия. Не скажу, что я долго задумывался над выбором простого почти «копипастного» небольшого решения или полноценного обобщения. Как правило к макросам стараюсь прибегнуть как можно реже, когда их выгода сильно перекрывает их недостатки или вообще их существование. В то же время полностью не чураюсь их, и если без них никак не достичь желаемого результата, то «пачкаюсь ими по полной» и осознанно, как пример тому еще один мой пост. Где без макросов достичь желаемого минимализма у меня никак не получилось.
Спасибо за ответ.
А по поводу 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)): результат;

На всякий случай — зарепортил
Да, забыл добавить, что с 10й студией — компилируется, но выдаёт неправильный результат(0 вместо 1)
Кстати, спасибо за наведение на мысль. Учту в дальнейшем рефакторинге. Приведенный материал по линк я все же по небольшими порциями, но развиваю…
Попроще, чем у 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
Круть. Компилит ток гад, долго.
Эхх накрутили они в С++11 с этими темплейтами, лучше б нормально это все через прекомпилятор/макросы реализовали.
Компилирование рекурсивных алгоритмов при большом размере контейнера будет зависеть от установленной максимальной глубины рекурсии. К примеру, у меня clang-700-1.81 и по дефолту максимальная глубина равна 256. Соотвественно, при размере контейнера примерно >100 лимит рекурсии превышается (все это я, естественно, профайлил). Конечно, можно увеличить глубину, но размер контейнера все равно будет ограничен возможностями компилятора и ОС.
Sign up to leave a comment.

Articles