Re[7]: Modern C++ Design ParameterType
От: Кодт Россия  
Дата: 08.10.12 19:26
Оценка:
Здравствуйте, rpz, Вы писали:

К>>// функции над типами

rpz>Это немного не то.. Тут предполагается что мы заранее знаем будущий тип, точнее его модификацию(AddStar или RemoveStar). Такие же штуки есть в новой STL. remove_reference и add_reference. А нужно вывести его автоматически.

Это я просто показывал — как могут использоваться функции (в том числе — идентичная функция) в функциях высшего порядка. Не более того.


К>>Во-вторых, мы можем передать в шаблон не сам тип, а его характеристику (traits). И шаблон оттуда извлечёт всё, что ему нужно, в том числе исходный тип.

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

Это если ты пишешь одноходовку. А если какую-нибудь навороченную конструкцию — то может оказаться, что traits передавать/запоминать удобнее.


К>>В-третьих, для нескольких разных типов будет одинаковый ParameterType. Особенно Александреску любил находить "наиболее подходящий способ передачи параметров". Для примитивных типов выгоднее передавать их в функцию по значению: foo(int). Для громоздких типов — по константной ссылке: foo(const std::string). Для неконстантных ссылок — как есть: foo(int&).

К>>Так что TypeTraits<std::string>::ParameterType может быть const std::string&.
rpz>Так идея-то как раз в этом, что по типу передаваемого значения определяется как передавать аргумент. В общем задача такова. Написать что-то типа
rpz>int i;
rpz>A a;
rpz>foo(i);//Вызывается foo(int) **1
rpz>foo(a);//Вызывается foo(const A&) **2

rpz>Но как я понял из вашего объяснения в таком виде задача нерешаема.

Задача как раз решаема, но
1) В некоторых случаях от неё больше вреда, чем пользы. Причём вред вплоть до стрельбы по памяти.
2) Нужно понять особенности ссылок в С++. Это не просто автоматически разыменованные указатели, как могло бы показаться, а довольно-таки специфическое природное явление.
По обоим пунктам столько слов уже написано, что прямо сейчас я отвлекаться не буду.

Итак, собственно, решение задачи. Возможно, не самое красивое, но уж какое мне пришло в голову в полодинадцатого ночи.
#include <iostream>
#include <typeinfo>

/////////////////////////
// инструменты для работы

#include <type_traits>
#include <boost/mpl/if.hpp>

// предикат, определяющий, какие типы мы будем передавать как есть (т.е. по значению):
// примитивные, указатели и ссылки (явно указанные ссылки будем передавать как ссылки)
template<class T> struct passed_by_value :
  std::integral_constant<bool, std::is_fundamental<T>::value | std::is_pointer<T>::value | std::is_reference<T>::value >
{};

// метафункция, возвращающая тип передаваемого аргумента по типу исходных данных
template<class T> struct argument_type : boost::mpl::if_< passed_by_value<T>, T, const T& > {};

//////////////////////////////////////////////////////////////
// бизнес-логика пусть будет здесь (чтобы ниже не повторяться)

template<class T> void foo_impl(T t) { std::cout << __PRETTY_FUNCTION__ << " : " << typeid(T).name() << std::endl; }
int x = 123;
std::string y = "hello";

void test_impl()
{
    foo_impl<int>(x);
    foo_impl<const std::string&>(y);
}

////////////////////////////////////////////////////////////
// лобовое решение - требует явно указывать параметр шаблона
template<class T> void foo0(typename argument_type<T>::type t) { foo_impl<typename argument_type<T>::type>(t); }

void test0()
{
  // ужасно!
  foo0<int>(x);
  foo0<std::string>(y);

  // для макроса сгодится (двойное упоминание аргумента)
  foo0<decltype(x)>(x);
  foo0<decltype(y)>(y);
}

///////////////////////////////////
// делаем чуть красивее - на SFINAE

template<class T> typename std::enable_if< passed_by_value<T>::value, void>::type foo1(T t)        { foo_impl<T>       (t); }
template<class T> typename std::enable_if<!passed_by_value<T>::value, void>::type foo1(const T& t) { foo_impl<const T&>(t); }
// плохо тем, что если нам нужна функция от двух и более аргументов, мы получим комбинаторный взрыв SFINAE

void test1()
{
  foo1(x);
  foo1(y);
}

////////////////////////////////////////////////////////////
// менее красиво, зато легче писать многоаргументные функции

// тип-обёртка для аргумента
template<class T> struct box_t
{
  typedef typename argument_type<T>::type type;
  type value;
  box_t(type v) : value(v) {}
};
// SFINAE здесь для того, чтобы последовательно провести политику партии :)
template<class T> typename std::enable_if< passed_by_value<T>::value, box_t<T> >::type box(T t)        { return box_t<T>(t); }
template<class T> typename std::enable_if<!passed_by_value<T>::value, box_t<T> >::type box(const T& t) { return box_t<T>(t); }

template<class T, class U> void foo2(box_t<T> t, box_t<U> u) // никакого SFINAE не потребовалось
{
  // не стал выдумывать, что б такого реализовать в этой функции - просто вызову нашу старую добрую дважды
  foo_impl<typename box_t<T>::type>(t.value); // вот, кстати: box_t выступил и объектом, и характеристикой типа
  foo_impl<typename box_t<U>::type>(u.value);
}

void test2()
{
  foo2(box(x), box(y));
}

// проверяем
int main()
{
  test_impl();
  test0();
  test1();
  test2();
}
Перекуём баги на фичи!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.