Все содержимое <boost/call_traits.hpp> объявлено в пространстве имен boost.
Шаблонный класс call_traits<T> инкапсулирует "наилучший" метод передачи параметра определенного типа T в или из функции, и состоит из собрания операторов typedef, определенных как показано в нижеследующей таблице. Предназначение call_traits заключается в создании гарантий, что проблемы типа "ссылка на ссылку" не возникнут, и что параметры передаются наиболее эффективным допустимым способом (см. примеры).
Заметьте, что для компиляторов, которые не поддерживают частичную специализацию или шаблоны-члены, использование шаблона call_traits не принесет никаких выгод. Дополнительно к этому, если компилятор поддерживает шаблоны-члены и не поддерживает частичную специализацию (Visual C++ 6), тогда call_traits не может быть использован с типами массивов (хотя он может быть использован для решения проблемы ссылка на ссылку).
существующая практика | эквивалент с call_traits | описание | замечания |
T |
| Определяет тип, который представляет "значение" типа T. Используйте для функций, которые возвращают значение, а также для хранения величин типа T. | 2 |
T& |
| Определяет тип, представляющий ссылку на тип T. Используйте для функций, которые должны возвращать T&. | 1 |
const T& |
| Определяет тип, представляющий константную ссылку на тип T. Используйте для функций, которые должны возвращать const T&. | 1 |
const T& |
| Определяет тип, который представляет "наилучший" способ передавать параметр типа T в функцию. | 1,333 |
Внимание:
Если T уже является типом ссылки, тогда call_traits определяется таким способом, что проблема ссылка на ссылку не возникает (требуется поддержка компилятором частичной специализации).
Если T является типом массива, тогда call_traits определяет value_type
как "константный указатель на тип" вместо "массива типов" (требуется частичная специализация). Обратите внимание, что если Вы используете value_type для хранения значения, то это приведет к хранению "константного указателя на массив" вместо хранения самого массива. Это может быть хорошим или плохим решением в зависимости от конкретной ситуации.
Если T это небольшой по размеру встроенный тип или указатель, тогда param_type
определяется как T const
, вместо T const&
. Это может улучшить шансы компилятора на оптимизацию циклов в теле функции, если они зависят от передаваемого параметра. В остальных случаях сематника передаваемого параметра остается неизменной. Для реализации этой возможности необходима частичная специализация.
Возможность создания копированием
Следующая таблица определяет, какие типы в шаблоне call_traits всегда могут создаваться копированием из других типов. Случаи, отмеченные '?', справедливы только если T можно конструировать копированием:
В: | |||||
Из: | T | value_type | reference | const_reference | param_type |
T | ? | ? | Y | Y | Y |
value_type | ? | ? | N | N | Y |
reference | ? | ? | Y | Y | Y |
const_reference | ? | N | N | Y | Y |
param_type | ? | ? | N | N | Y |
Если T тип с работающим присваиванием, то следующие виды присваивания возможны:
В: | |||||
Из: | T | value_type | reference | const_reference | param_type |
T | Y | Y | - | - | - |
value_type | Y | Y | -- | - | - |
reference | Y | Y | - | - | - |
const_reference | Y | Y | -- | - | - |
param_type | Y | Y | -- | - | - |
Следующая таблица показывает эффект, который получается применением call_traits к различным типам. Подразумевается, что компилятор поддерживает частичную специализацию: если это не так, то все типы ведут себя так же, как в графе для "myclass", и call_traits не может быть использован со ссылками или типами массивов.
тип-результат call_traits: | |||||
исходный тип T | value_type | reference | const_reference | param_type | может применяться для: |
myclass | myclass | myclass& | const myclass& | myclass const& | всех определенных пользователем типов. |
int | int | int& | const int& | int const | всех небольших встроенных типов. |
int* | int* | int*& | int*const& | int* const | любых указателей. |
int& | int& | int& | const int& | int& | любых ссылок. |
const int& | const int& | const int& | const int& | const int& | всех константных ссылок. |
int[3] | const int* | int(&)[3] | const int(&)[3] | const int* const | всех типов маккисов. |
const int[3] | const int* | const int(&)[3] | const int(&)[3] | const int* const | всех константных массивов. |
Пример 1:
Следующий класс является простейшим случаем, он хранит величину типа T (см. файл call_traits_test.cpp). Демонстрируется, как можно использовать все возможные типы в call_traits:
template <class T> struct contained { // define our typedefs first, arrays are stored by value // so value_type is not the same as result_type: typedef typename boost::call_traits<T>::param_type param_type; typedef typename boost::call_traits<T>::reference reference; typedef typename boost::call_traits<T>::const_reference const_reference; typedef T value_type; typedef typename boost::call_traits<T>::value_type result_type; // stored value: value_type v_; // constructors: contained() {} contained(param_type p) : v_(p){} // return byval: result_type value() { return v_; } // return by_ref: reference get() { return v_; } const_reference const_get()const { return v_; } // pass value: void call(param_type p){} };
Пример 2 (проблема ссылки на ссылку):
Рассмотрим определение std::binder1st:
template <class Operation> class binder1st : public unary_function<typename Operation::second_argument_type, typename Operation::result_type> { protected: Operation op; typename Operation::first_argument_type value; public: binder1st(const Operation& x, const typename Operation::first_argument_type& y); typename Operation::result_type operator()(const typename Operation::second_argument_type& x) const; };
Теперь посмотрим, что будет в относительно общем случае, когда функтор получает свой второй аргумент как ссылку, что подразумевает, что Operation::second_argument_type
является типом ссылки, operator()
теперь определяется как получающий ссылку на ссылку, что некорректно для текущего стандарта C++. Решение заключается в том, чтобы модифицировать определение operator() с использованием call_traits:
typename Operation::result_type operator()(typename call_traits<typename Operation::second_argument_type>::param_type x) const;
Теперь в случае, если Operation::second_argument_type
является типом ссылки, аргумент передается как ссылка, и проблема "ссылка на ссылку" не возникает.
Пример 3 (проблема с make_pair):
Если мы передает имя массива как один (или оба) аргумента в std::make_pair
, тогда алгоритм вывода аргументов шаблона воспринимает преданный параметр как "константная ссылка на массив типов T", это также применимо к строковым литералам (которые на самом деле также являются массивами). Следовательно вместо возврата пары указателей, происходит возврат пары массивов, а так как тип массивов не может создаваться копированием, то код не компилируется. Одним решением может быть явное преобразование аргументов make_pair к указателям, но call_traits дает лучшее (т.е. автоматическое) решение (которое безопасно в обобщенном коде, где преобразования могут привести к неверному результату):
template <class T1, class T2> std::pair< typename boost::call_traits<T1>::value_type, typename boost::call_traits<T2>::value_type> make_pair(const T1& t1, const T2& t2) { return std::pair< typename boost::call_traits<T1>::value_type, typename boost::call_traits<T2>::value_type>(t1, t2); }
Здесь полученные при выводе типы аргументов будут автоматически усечены до указателей, если выведенные типы являются массивами. Аналогичная ситуация возникает со стандартными байндерами и адаптерами (binders and adapters): в принципе, это справедливо для любой функции, которая "обертывает" временный объект, чей тип выводится. Обратите внимание, аргументы функции для make_pair не выражаются в терминах call_traits: это могло бы привести к невозможности вывести аргументы шаблона.
Пример 4 (оптимизация алгоритма fill):
Шаблон call_traits может "оптимизировать" передачу небольших встроенных встроенных типов как аргументов функции. В следующем примере (см. fill_example.cpp), версия std::fill оптимизируется двумя способами: если передаваемый тип является встроенным однобайтовым типом, то используется std::memset, иначе используется обычная реализация алгоритма копирования, но с применением "оптимизации" передачи параметров через call_traits:
namespace detail{ template <bool opt> struct filler {{ template <typename I, typename T> static void do_fill(I first, I last, typename boost::call_traits<T>::param_type val); { while(first != last) { *first = val; ++first; } } }; template <> struct filler<true> { template <typename I, typename T> static void do_fill(I first, I last, T val) { memset(first, val, last-first); } }; } template <class I, class T> inline void fill(I first, I last, const T& val) { enum{ can_opt = boost::is_pointer<I>::value && boost::is_arithmetic<T>::value && (sizeof(T) == 1) }; typedef detail::filler<can_opt> filler_t; filler_t::template do_fill<I,T>(first, last, val);}}
Примечение: причина того, что "оптимальным" небольших встроенных типов является передача как T const вместо const T& заключается в том, что компилятор может определить, что величина неизменна и для нее нет синонимов (aliases). С такой информацией компилятор способен закэшировать передаваемое значение в регистре, развернуть (unroll) цикл, или использовать параллельные инструкции. Точнее говоря, эффект от применения такой оптимизации зависит от компилятора.
Обратите внимание, аргументы функции для make_pair не выражаются в терминах call_traits: это могло бы привести к невозможности вывести аргументы шаблона. Вместо этого fill работает как "тонкая обертка", предназначенная для выполнения вывода аргументов шаблона. В любом случае компилятор соптимизирует вызов fill, заменив его на вызов filler<>::do_fill, который использует call_traits.
Обоснование
Следующие замечанию кратко описывают причины выбора решений в call_traits.
Все определенные пользователем типы следуют "существующей практике" и не нуждаются в комментариях.
Небольшие встроенные типы (то, что стандарт называет фундаментальными типами [3.9.1]) отличаются от существующей практики только реализацией call_traits<>::param_type. В этом случае передача "T const" совместима с существующей практикой, но может улучшить эффективность в некоторых случаях (см. Пример 4), в любом случае это не должно быть хуже, чем существующая практика.
Указатели ведут себя как небольшие встроенные типы.
Для ссылочных типов реализация следует примеру 2 - ссылки на ссылки не допускаются, так что call_traits предотвращает возникновение этой проблемы. Есть предложение изменить C++ таким образом, чтобы ссылка на ссылку была эквивалентна одной ссылке (предложение #106, сделано Bjarne Stroustrup), call_traits<T>::value_type и call_traits<T>::param_type обеспечивают этот же предложенный эффект, без необходимости менять язык.
Для типов массивов, функция, которая получает массив как аргумент, усекает его до указателя: это ознчает, что тип действительного параметра отличается от объявленного типа. Иногда это может вызвать проблемы в коде шаблона, который опирается на объявленный тип параметра. К примеру:
template <class T> struct A { void foo(T t); };
В данном случае если мы конкретизируем A<int[2]>, тогда объявленный тип параметра, передаваемого в функцию-метод foo будет int[2], но действительный тип будет const int*. Если мы попытаемся использовать тип T внутри тела функции, тогда есть большая вероятность, что наш код не скомпилируется:
template <class T> void A<T>::foo(T t) { T dup(t); // doesn't compile for case that T is an array. }
При использовании call_traits усечение массива к указателю является явным, и тип параметра тот же самый, что объявленный тип:
template <class T> struct A { void foo(typename call_traits<T>::value_type t); }; template <class T> void A<T>::foo(typename call_traits<T>::value_type t) { typename call_traits<T>::value_type dup(t); // OK even if T is an array type. }
Для value_type (возврат по значению) опять-таки только массив может быть возвращен, а не целый массив, и снова call_traits делает усечени явным. Член value_type полезен в любом случае, когда массив должен быть явно усечен до указателя - пример 3 показывает тестовый случай (примечание: специализация с массивом для call_traits наименее понятна из всех специализаций call_traits; если данная семантика вызывает у Вас какие-то трудности, или не решает какую-то проблему с массивами, тогда я буду рад услышать об этом. Большинству разработчиков данная специализация не нужна).
P.S.: Нагло спёрто отсюда: BOOST C++: заголовочный файл <boost/call_traits.hpp>
Комментариев нет:
Отправить комментарий