В продолжение
Как бы реализовать замыкания?Автор: remark
Дата: 17.07.09
В крации, необходимо реализовать замыкания, так что бы:
— неправильные типы аргументов детектируются в месте создания замыкания
— автоматически определяется наиболее адекватный способ как хранить аргумент, т.е. не надо ref/cref как в boost
— отсутствует лишняя работа во время выполнения
— кол-во шаблонов N, а не K^N
— необходима поддержка "опций" для аргументов, типа pass_by_pointer(x), delete_after_use(х) и т.д.
Судя по всему реализовать чисто на шаблонах это не получится. Поэтому сейчас я остановился на следующей технике.
Тип аргумента, передаваемый пользователем, (х, pass_by_pointer(x), delete_after_use(х) и т.д.) определяется в ран-тайм. Для этого с каждым аргументом дополнительно передаётся unsigned type = 0/1/2, который кодирует тип аргумента (х/pass_by_pointer(x)/delete_after_use(х)). Далее сразу после получения всех этих ран-тайм type, они преобразуются в параметры шаблона. В данном конкретном случае с помощью рекурсивных шаблонных функций, но в принципе можно применить и switch, и "лесенку if'ов". Идея в том, что преобразование это прекрасно инлайниться и устраняется, т.к. в реальности-то типы у нас известны на этапе компиляции. Т.е. фактически мы как бы компайл-тайм информацию временно делаем ран-тайм (по-крайней мере формально), а потом обратно "возвращаем" в ранг компайл-тайм информации. При этом при релиз компиляции мы не получаем никаких дополнительных накладных расходов, но зато существенно расширяем возможности шаблонов (как бы).
В общем в итоге я получил работающий прототип, который отвечает всем указанным требованиям.
Вот небольшой пример использования. Тут spawn() сразу же напрямую вызывает переданную функцию, Х — тип пустышка. Как видно, код получился оптимальный.
void bar(int x, X* y);
int main()
{
spawn(bar, 5, delete_after_use(new X));
00401071 push 1
00401073 call operator new (401384h)
00401078 mov esi,eax
0040107A push esi
0040107B push 5
0040107D call bar (401000h)
00401082 push esi
00401083 call dword ptr [__imp__free (4020D0h)]
00401089 add esp,10h
}
Ниже я приведу полную реализацию, но она достаточно запутанная, и была сделана с единственной целью быть proof-of-concept. Определенно её можно сильно причесать, но это уже дело техники. Поэтому прошу тут сильно не придираться. Особое внимание обратите на класс wrapper, который кодирует тип в виде ран-тайм значения; и на класс helper, который производит обратное преобразование в компайл-тайм значение. Всё остальное — это туева хуча различных хелперов и обёрток.
Код рабочий, можно его скомпилировать и поиграться.
template<typename T>
struct param_traits
{
typedef T param;
};
template<typename T>
struct pass_by_pointer_t
{
typename param_traits<T>::param obj;
};
template<typename T>
pass_by_pointer_t<T> pass_by_pointer(typename param_traits<T>::param obj)
{
pass_by_pointer_t<T> res = {obj};
return res;
}
template<typename T>
struct delete_after_use_t
{
typename param_traits<T>::param obj;
};
template<typename T>
void delete_after_use(T obj)
{
}
template<typename T>
delete_after_use_t<T*> delete_after_use(T* obj)
{
delete_after_use_t<T*> res = {obj};
return res;
}
template<typename T>
struct null_option_t
{
static void after_execute(typename param_traits<T>::param) {}
};
template<typename T>
struct pass_by_pointer_option_t
{
static void after_execute(typename param_traits<T>::param obj)
{
}
};
template<typename T>
struct delete_after_use_option_t
{
static void after_execute(typename param_traits<T>::param obj)
{
}
};
template<typename T>
struct delete_after_use_option_t<T*>
{
static void after_execute(typename param_traits<T*>::param obj)
{
obj->~T();
free(obj);
}
};
template<typename T>
struct wrapper
{
unsigned type;
typename param_traits<T>::param v;
wrapper(typename param_traits<T>::param v)
: type (0)
, v (v)
{}
wrapper(pass_by_pointer_t<T> v)
: type (1)
, v (v.obj)
{}
wrapper(delete_after_use_t<T> v)
: type (2)
, v (v.obj)
{}
};
template<size_t idx>
struct get_arg_type;
template<> struct get_arg_type<1>
{
static unsigned type(unsigned type1, unsigned type2, unsigned type3)
{
return type1;
}
};
template<> struct get_arg_type<2>
{
static unsigned type(unsigned type1, unsigned type2, unsigned type3)
{
return type2;
}
};
template<> struct get_arg_type<3>
{
static unsigned type(unsigned type1, unsigned type2, unsigned type3)
{
return type3;
}
};
template<bool, typename F, typename T>
struct if_t
{
typedef F type;
};
template<typename F, typename T>
struct if_t<true, F, T>
{
typedef T type;
};
template<size_t total_arg_count, size_t current_arg_idx, typename A1, typename A2 = void*, typename A3 = void*, typename W1 = void, typename W2 = void, typename W3 = void>
struct helper
{
__forceinline
static void exec(void* f, unsigned type1, unsigned type2, unsigned type3, typename param_traits<A1>::param a1, typename param_traits<A2>::param a2, typename param_traits<A3>::param a3)
{
unsigned type = get_arg_type<current_arg_idx>::type(type1, type2, type3);
if (type == 0)
helper<total_arg_count, current_arg_idx + 1, A1, A2, A3,
if_t<current_arg_idx == 1, W1, null_option_t<A1> >::type,
if_t<current_arg_idx == 2, W2, null_option_t<A2> >::type,
if_t<current_arg_idx == 3, W3, null_option_t<A3> >::type>
::exec(f, type1, type2, type3, a1, a2, a3);
else if (type == 1)
helper<total_arg_count, current_arg_idx + 1, A1, A2, A3,
if_t<current_arg_idx == 1, W1, pass_by_pointer_option_t<A1> >::type,
if_t<current_arg_idx == 2, W2, pass_by_pointer_option_t<A2> >::type,
if_t<current_arg_idx == 3, W3, pass_by_pointer_option_t<A3> >::type>
::exec(f, type1, type2, type3, a1, a2, a3);
else if (type == 2)
helper<total_arg_count, current_arg_idx + 1, A1, A2, A3,
if_t<current_arg_idx == 1, W1, delete_after_use_option_t<A1> >::type,
if_t<current_arg_idx == 2, W2, delete_after_use_option_t<A2> >::type,
if_t<current_arg_idx == 3, W3, delete_after_use_option_t<A3> >::type>
::exec(f, type1, type2, type3, a1, a2, a3);
}
};
template<typename A1, typename A2, typename A3, typename W1, typename W2, typename W3>
struct helper<1, 2, A1, A2, A3, W1, W2, W3>
{
__forceinline
static void exec(void* f, unsigned type1, unsigned type2, unsigned type3, typename param_traits<A1>::param a1, typename param_traits<A2>::param a2, typename param_traits<A3>::param a3)
{
typedef void(*real_t)(A1);
real_t real_f = (real_t)f;
real_f(a1);
W1::after_execute(a1);
}
};
template<typename A1, typename A2, typename A3, typename W1, typename W2, typename W3>
struct helper<2, 3, A1, A2, A3, W1, W2, W3>
{
__forceinline
static void exec(void* f, unsigned type1, unsigned type2, unsigned type3, typename param_traits<A1>::param a1, typename param_traits<A2>::param a2, typename param_traits<A3>::param a3)
{
typedef void(*real_t)(A1, A2);
real_t real_f = (real_t)f;
real_f(a1, a2);
W1::after_execute(a1);
W2::after_execute(a2);
}
};
template<typename T>
struct id
{
typedef T type;
};
template<typename A1, typename A2>
__forceinline
void spawn(void(*f)(A1, A2), typename id<wrapper<A1> >::type a1, typename id<wrapper<A2> >::type a2)
{
helper<2, 1, A1, A2>::exec(f, a1.type, a2.type, 0, a1.v, a2.v, 0);
}
void foo(int x, std::string const& y)
{
std::cout << "foo(" << x << ", " << y << ")" << std::endl;
}
struct X
{
X()
{
#ifdef _DEBUG
std::cout << __FUNCSIG__ << std::endl;
#endif
}
~X()
{
#ifdef _DEBUG
std::cout << __FUNCSIG__ << std::endl;
#endif
}
};
__declspec(noinline)
void bar(int x, X* y)
{
std::cout << "bar(" << x << ", " << "X" << ")" << std::endl;
}
int main()
{
//std::string str = "hello";
//spawn(foo, 5, str);
spawn(bar, 5, delete_after_use(new X));
}