Мотивировочную часть пропустим, перейдём к фактической.
Дано:
— с одной стороны, формальный тип аргумента (у тебя — выведенный из сигнатуры функции)
— с другой стороны, фактический тип, который может быть в обёртке
Нужно:
— аккуратно протащить информацию об обёртке.
Исходный нерабочий прототип
template<class Formal> // этот параметр - явный, а не выводится из фактического аргументаvoid foo(argument_traits<Formal>::type arg)
{
argument_traits<Formal>::type storage(arg);
.....
}
Лобовое решение
template<class Formal, class Real> // Formal задаётся явно, Real выводитсяvoid foo(Real const& arg) // здесь мы на всякий случай передаём по ссылке, чтобы случайно не сделать копирование
{
storage_traits<Formal,Real>::type storage(arg); // а уже в недрах - делаем что должно
.....
}
struct wrapper_base {}; // от него должны унаследоваться все обёрткиstruct is_wrapper<T> : is_base_and_derived< wrapper_base, T > {};
// конкретные обёртки (make-функции для них подразумеваются)template<class T> struct byref_t : wrapper_base { ..... };
template<class T> struct byvalue_t : wrapper_base { ..... };
template<class T> struct finally_delete_t : wrapper_base { ..... };
// а вот, собственно, возможная реализация storage_traitstemplate<class Formal, class Real>
struct storage_traits :
if_<
is_wrapper<Real>,
Real, // если обёртка чего бы то ни было - используем копию обёртки
argument_traits<Formal>::type // иначе используем оптимизированную передачу формального аргумента
>
{};
Вот что-то в таком роде.
Но я, после того, как намаялся с Loki, очень и очень зауважал подход boost. Там, по крайней мере, никаких сюрпризов с владением связанных аргументов.
Первоначально предполагалось реализовать замыкания как:
template<typename T>
struct param_traits
{
// определяет как передавать и хранить аргумент
};
template<typename T>
struct param_traits<T const&>
{
//...
};
template<typename T>
struct param_traits<T&>
{
//...
};
template<typename A1, typename A2>
void spawn (void(*f)(A1, A2), typename param_traits<A1>::param_type a1, typename param_traits<A2>::param_type a2)
{
// запоминаем f, a1, a2 для последующего исполнения
}
// аналогичные функции spawn() для другого кол-ва аргументов
Важные преимущества:
— неправильные типы аргументов детектируются в месте вызова spawn
— автоматически определяется наиболее адекватный способ как хранить аргумент, т.е. не надо ref/cref как в boost
— отсутствует лишняя работа во время выполнения
— всего 1 шаблон spawn() для 2 аргументов
Теперь надо добавить различные хинты к аргументам, что-то типа такого:
X* x = new X (...);
spawn(foo, delete_after_execution(x));
или:
spawn(foo, pass_by_pointer(x)); // перекрываем способ передачи по-умолчанию
Не понимаю, как это сделать без потери указанных преимуществ.
Есть какие-нибудь идеи?
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, remark, Вы писали:
R>>Не понимаю, как это сделать без потери указанных преимуществ. R>>Есть какие-нибудь идеи?
А>А в чём проблемы у make-фнкции delete_after_execution, которая тоже использует эти же param_traits?
Что-то типа такого и хотелось бы получить. Но проблема в том, что param_traits никогда не будут параметризованы delete_after_execution_t, т.к. они параметризуются типом параметра функции, а не типом аргумента. Текущий spawn() полностью слеп к типам аргументов, это с одной стороны хорошо, т.к. все преобразования, варнининги и ошибки выдаются в строчке вызова spawn(); но с другой — не получается реализовать хинты.
У меня пока получилась только вот такая фигня.
Что в нём не нравится, так это то, что он без церемонно снимает const с аргументов, соотв. может скомпилировать код, который не должен компилироваться.
Плюс варнинги насчёт конвертации аргументов теперь выдаются внутри spawn, т.к. формально is_converible проверка для них проходит.
Возможно тут ещё что-то есть, например, для каких-то функций и аргументов будет вызываться не тот spawn.
#pragma warning (push)
#pragma warning (disable : 4244)
template<typename To, typename From>
struct is_converible
{
static char test (To);
static long test (...);
static bool const result = (sizeof(test(*(From*)0)) == sizeof(char));
};
#pragma warning (pop)
template<bool>
struct enable_if
{
};
template<>
struct enable_if<true>
{
typedef void type;
};
template<typename T>
struct identity
{
typedef T type;
};
template<typename T>
struct param
{
param(T const&) {}
};
template<typename A1, typename A2, typename R1, typename R2>
// этот вариант работет только если внутри мы сможем сконвертировать все аргументыtypename enable_if<is_converible<A1, R1>::result && is_converible<A2, R2>::result>::type
spawn (void (*f) (A1, A2), R1 const& a1, R2 const& a2)
{
f(const_cast<R1&>(a1), const_cast<R2&>(a2));
}
// а этот вариант исключительно для выдачи ошибок конвертацииtemplate<typename A1, typename A2>
void
spawn (void (*f) (A1, A2), typename identity<param<A1> >::type a1, typename identity<param<A2> >::type a2);
Я укрепляюсь в мысли, что на С++ шаблонах это сделать нельзя. Не подумайте, что я придираюсь ради пустого придирательства, просто имеются требования, которые не зависят от меня, и уже имеется реализация, которая обозначена в первом посте (и которая соотв. обладает указанными там преимуществами).
По поводу варианта IROV... Во-первых, правильно ли я понял, что type_check есть is_convertible?
Не нравится, что будут делаться копии копии аргументов, т.к. параметры передаются по значению.
Насколько я понимаю, что так не получится передавать ссылки, т.к. в целевую функцию пойдёт ссылка на неправильный объект.
Вырнинги при конвертации будут где-то в дебрях реализации.
ПО поводу варианта Кодт.
Не нравится, что можем скомпилировать некорректный код. Например, целевая функция принимает string&, пользователь передаёт string const&, мы по-тихому снимаем константность и успешно компилируем это. По-скольку мы принимаем T const& мы не знаем, что пользователь нам передал — то ли T const&, то ли T&.
Не нравится, что ошибки конвертации и варнинги будут внутри библиотечного кода.