Неправильно определяются типы при передаче в функцию.
template<typename T>
void funcBy(T)
{
std::cout<<"parameter is ref: "<<((client::A_TypeTraits<T>::isRef)?"true":"false")<<std::endl;
std::cout<<"parameter is Cref: "<<((client::A_TypeTraits<T>::isCRef)?"true":"false")<<std::endl;
std::cout<<"parameter is by val: "<<((client::A_TypeTraits<T>::isFundamental)?"true":"false")<<std::endl<<std::endl;
}
A a;
A& ra = a;
funcBy(client::A_TypeTraits<int>::ParameterType());
funcBy(client::A_TypeTraits<A>::ParameterType(a));
funcBy(client::A_TypeTraits<A&>::ParameterType(ra));
Только первый вызов выводит false false true. Остальные 2 выводят только false.
Хотя по отдельности работает правильно
Здравствуйте, Аноним, Вы писали:
А>Есть класс свойств типа.
Это упражнение или велосипед? Если велосипед, то стоит посмотреть на boost::mpl, а если упражнение, то будем разбираться.
А>Неправильно определяются типы при передаче в функцию. А>
А>template<typename T>
А>void funcBy(T)
А>{
А>std::cout<<"parameter is ref: "<<((client::A_TypeTraits<T>::isRef)?"true":"false")<<std::endl;
А>std::cout<<"parameter is Cref: "<<((client::A_TypeTraits<T>::isCRef)?"true":"false")<<std::endl;
А>std::cout<<"parameter is by val: "<<((client::A_TypeTraits<T>::isFundamental)?"true":"false")<<std::endl<<std::endl;
А>}
А>A a;
А>A& ra = a;
А> funcBy(client::A_TypeTraits<int>::ParameterType());
А> funcBy(client::A_TypeTraits<A>::ParameterType(a));
А> funcBy(client::A_TypeTraits<A&>::ParameterType(ra));
А>
А>Только первый вызов выводит false false true. Остальные 2 выводят только false.
Дело в том, что тип T у функции выводится. Поскольку funcBy принимает "нечто типа T" по значению, то, что бы ей на вход ни подсунули, первым делом произойдёт неявное приведение к rvalue. А если lvalue->rvalue невозможно из-за приватного конструктора копирования, то будет ошибка компиляции.
Поэтому в первой строке T=int, во второй и третьей T=A. Даже если подсунуть туда const A, — всё равно const lvalue -> rvalue потеряет константность.
Кстати сказать, у переменной a и ссылки на ней ra тип один и тот же — это A, а вовсе не A&.
Если же явно указывать параметр funcBy<int>, funcBy<A>, funcBy<A&>, funcBy<const A&> — то вывод будет ожидаемо разный.
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, Аноним, Вы писали:
А>>Есть класс свойств типа.
К>Это упражнение или велосипед? Если велосипед, то стоит посмотреть на boost::mpl, а если упражнение, то будем разбираться.
Это упражнение, конечно.
К>Дело в том, что тип T у функции выводится. Поскольку funcBy принимает "нечто типа T" по значению, то, что бы ей на вход ни подсунули, первым делом произойдёт неявное приведение к rvalue. А если lvalue->rvalue невозможно из-за приватного конструктора копирования, то будет ошибка компиляции. К>Поэтому в первой строке T=int, во второй и третьей T=A. Даже если подсунуть туда const A, — всё равно const lvalue -> rvalue потеряет константность.
Да.. с выводом что-то затупил. Только вот про rvalue.. Почему к rvalue то приводится?
Берется адрес переменной, создается ссылка на нее. Значит i — lvalue. Или я не так понял что-то?
К>Кстати сказать, у переменной a и ссылки на ней ra тип один и тот же — это A, а вовсе не A&.
Да, действительно
std::cout<<(typeid(ii)==typeid(rii))<<std::endl;
выводит true. А почему так сделано?
К>Если же явно указывать параметр funcBy<int>, funcBy<A>, funcBy<A&>, funcBy<const A&> — то вывод будет ожидаемо разный.
Т.е. получается только таким образом можно использовать ParameterType? С явным указанием параметра?
Здравствуйте, rpz, Вы писали:
rpz>Да.. с выводом что-то затупил. Только вот про rvalue.. Почему к rvalue то приводится?
Потому что функция объявлена так: fooo(T). Если бы была fooo(T&) — то принимала бы lvalue.
rpz>
rpz>Берется адрес переменной, создается ссылка на нее. Значит i — lvalue. Или я не так понял что-то?
Это адрес локальной копии, а не внешнего оригинала.
К>>Кстати сказать, у переменной a и ссылки на ней ra тип один и тот же — это A, а вовсе не A&. rpz>Да, действительно rpz>А почему так сделано?
Ну, так уж сделано. Точного объяснения я не помню, нужно вчитываться в отцов-основателей (т.е. Страуструпа).
К>>Если же явно указывать параметр funcBy<int>, funcBy<A>, funcBy<A&>, funcBy<const A&> — то вывод будет ожидаемо разный. rpz>Т.е. получается только таким образом можно использовать ParameterType? С явным указанием параметра? rpz>
Здравствуйте, Кодт, Вы писали:
К>Конечно, ерунда. Нужно было просто написать funcBy(ra) или funcBy<A&>(ra). Зачем эту косвенность, да ещё с созданием временной копии?
Ну да,отправил уже пост и тут же понял что так надо писать и все.
Но остается вопрос зачем вообще тогда Александреску вводит такой тип у TypeTraits? По идее то он сделан для того чтобы можно было определить тип параметра и соответственно или оставить ссылку или оставить по значению или сделать константную ссылку. А получается что так его использовать нельзя. Или все же можно как-то?
Здравствуйте, rpz, Вы писали:
rpz>Но остается вопрос зачем вообще тогда Александреску вводит такой тип у TypeTraits? По идее то он сделан для того чтобы можно было определить тип параметра и соответственно или оставить ссылку или оставить по значению или сделать константную ссылку. А получается что так его использовать нельзя. Или все же можно как-то?
Во-первых, есть такая функция — "идентичность". id(x)=x. Может пригодиться, когда мы используем её как параметр другой функции:
// функции над даннымиint id(int x) { return x; }
int succ(int x) { return x+1; }
int pred(int x) { return x-1; }
void foo(int(*f)(int)) { std::cout << f(123) << std::endl; }
// функции над типамиtemplate<class T> struct Id { typedef T type; };
template<class T> struct AddStar { typedef T* type; };
template<class T> struct RemoveStar { typedef T type; };
template<class T> struct RemoveStar<T*> { typedef T type; };
template< template<class> class Fun > void Foo()
{
std::cout << typeid( typename Fun<int***>::type ).name() << std::endl;
}
int main()
{
foo(id); foo(succ); foo(pred);
Foo<Id>(); Foo<AddStar>(); Foo<RemoveStar>();
}
Во-вторых, мы можем передать в шаблон не сам тип, а его характеристику (traits). И шаблон оттуда извлечёт всё, что ему нужно, в том числе исходный тип.
В-третьих, для нескольких разных типов будет одинаковый ParameterType. Особенно Александреску любил находить "наиболее подходящий способ передачи параметров". Для примитивных типов выгоднее передавать их в функцию по значению: foo(int). Для громоздких типов — по константной ссылке: foo(const std::string). Для неконстантных ссылок — как есть: foo(int&).
Так что TypeTraits<std::string>::ParameterType может быть const std::string&.
На самом деле, как показала практика, Александреску здесь подложил грабли с длинными гвоздями, запутанными в траве. И уже в boost::bind / boost::function отказались от "хитроумной" передачи параметров, предпочитая всегда по значению, либо явно указывая способ передачи.
К>// функции над типами
К>template<class T> struct Id { typedef T type; };
К>template<class T> struct AddStar { typedef T* type; };
К>template<class T> struct RemoveStar { typedef T type; }; К>template<class T> struct RemoveStar<T*> { typedef T type; };
К>template< template<class> class Fun > void Foo() К>{ К> std::cout << typeid( typename Fun<int***>::type ).name() << std::endl; К>}
К>int main() К>{ К> foo(id); foo(succ); foo(pred); К> Foo<Id>(); Foo<AddStar>(); Foo<RemoveStar>(); К>} К>[/c]
Это немного не то.. Тут предполагается что мы заранее знаем будущий тип, точнее его модификацию(AddStar или RemoveStar). Такие же штуки есть в новой STL. remove_reference и add_reference. А нужно вывести его автоматически. К>Во-вторых, мы можем передать в шаблон не сам тип, а его характеристику (traits). И шаблон оттуда извлечёт всё, что ему нужно, в том числе исходный тип. К>
Ну а толку, если мы уже передали переменную и передалась она по значению из-за вывода аргументов шаблона. Тут надо заставить менться тип аргумента принимающей функции.
К>В-третьих, для нескольких разных типов будет одинаковый ParameterType. Особенно Александреску любил находить "наиболее подходящий способ передачи параметров". Для примитивных типов выгоднее передавать их в функцию по значению: foo(int). Для громоздких типов — по константной ссылке: foo(const std::string). Для неконстантных ссылок — как есть: foo(int&). К>Так что TypeTraits<std::string>::ParameterType может быть const std::string&.
Так идея-то как раз в этом, что по типу передаваемого значения определяется как передавать аргумент. В общем задача такова. Написать что-то типа
int i;
A a;
foo(i);//Вызывается foo(int) **1
foo(a);//Вызывается foo(const A&) **2
ну или хотя бы так
foo(ParameterTraits<int>::ParameterType(i));// **1
foo(ParameterTraits<A>::ParameterType(a));// **2
но не
foo<const A&>(a) или foo(ParameterTraits<const A&>::ParameterType(a));
//То есть мы не подсказываем компилятору foo<const A&>(a). Это выводится само через шаблон ParameterType<T> (T=A).
Но как я понял из вашего объяснения в таком виде задача нерешаема.
Здравствуйте, 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>Но как я понял из вашего объяснения в таком виде задача нерешаема.
Задача как раз решаема, но
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);
}
///////////////////////////////////
// делаем чуть красивее - на SFINAEtemplate<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); }
// плохо тем, что если нам нужна функция от двух и более аргументов, мы получим комбинаторный взрыв SFINAEvoid 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();
}
Здравствуйте, rpz, Вы писали:
rpz>Круто ) Не до конца понял, как работает во втором случае, но думаю разберусь со временем. Спасибо
Во втором — это foo1 или foo2? (Считаем с нуля или с единицы? )
foo1:
У нас есть две сигнатуры, foo1(T) и foo1(const T&) — наиболее эффективно, на наш взгляд, принимающие аргумент.
Для компилятора вызов foo1(z) неоднозначен (чем бы ни был z — литералом, временным объектом или переменной), поскольку преобразования lvalue->rvalue и rvalue->const-lvalue не влияют на его выбор. Получаем ошибку компиляции.
Чтобы такого не случилось, сделаем так, что для каждого типа будет доступна только одна сигнатура. Это техника SFINAE (substitution failure is not an error).
SFINAE делает так, что, пытаясь вывести параметр шаблона из типа аргумента, мы получаем что-нибудь полезное, если это нужная сигнатура, и какую-нибудь ерунду, если ненужная.
В частности, std::enable_if определён так
template<bool, class> struct enable_if {};
template<class T> struct enable_if<true, T> { typedef T type; }; // только для true существует зависимое имя type
У нас есть две точки в сигнатуре функции, куда мы можем впрячь этот трюк.
1) возвращаемый тип (чаще всего так и делается) — вместо void foo1(T) напишем typename std::enable_if<condition,void>::type foo1(T)
2) необязательный аргумент: void foo1(T, void* = NULL) — заменим void на typename... как выше
На ненужной сигнатуре компилятор не найдёт зависимое имя enable_if::type, это будет признано сбоем подстановки параметров шаблона.
foo2:
Я делаю допущение, что 2-3 раза скопировать примитивный тип — дешевле, чем передать его по ссылке и затем заплатить цену косвенной адресации в теле функции.
box_t<T> — обёртка аргумента, которая эффективно хранит его: по значению или по ссылке. Стоит ли говорить, что конструктор этой структуры эффективно принимает свой аргумент?
Первый шаг: вместо foo(z) мы пишем foo(box_t<decltype(z)>(z)).
Здесь box_t передаётся по значению, но, поскольку это структура размером с примитивный тип (тип аргумента либо указатель на тип аргумента), то накладные расходы почти те же самые.
Второй шаг: ленимся указывать параметр box_t. Для этого прибегаем к конструирующей функции box(), где параметр выводится из типа аргумента
Оставить только одну версию функции box мы не можем и не хотим.
Не можем первую (ибо там мы делаем лишнее копирование тяжёлого аргумента, да ещё и возвращаем ссылку на локальную копию — а это верная дорога в UB).
Не хотим вторую (ибо там мы делаем косвенную адресацию примитивного аргумента).
Что нам делать? Да конечно же, SFINAE!
В итоге, получаем вот что.
1) Сигнатура рабочей функции всегда одна: foo(box<X> x, box<Y> y, box<Z> z, etc.)
2) Мы можем позволить себе писать шаблон рабочей функции: template<class X, class Y, class Z> void foo(box<X> x, box<Y> y, box<Z> z)
Казалось бы, а что нам мешало избавиться от box, сразу сказав foo(typename box<X>::type x, etc.) ?
Да, если мы не собираемся выводить тип X (если foo — не шаблон, или если это нешаблонный член шаблона класса), то так было бы гораздо лучше.
Но для вывода типа такое недопустимо. Компилятор в принципе не решает уравнения на зависимых именах, т.е. "найти X такое, что box<X>::type == int", а тем более, "... == int либо const int&"
Вот поэтому и пришлось прибегнуть к обёртыванию параметров.