Все содержимое <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; если данная семантика вызывает у Вас какие-то трудности, или не решает какую-то проблему с массивами, тогда я буду рад услышать об этом. Большинству разработчиков данная специализация не нужна).
Revised 01 September 2000
© Copyright boost.org 2000. Permission to copy, use, modify, sell and distribute this document is granted provided this copyright notice appears in all copies. This document is provided "as is" without express or implied warranty, and with no claim as to its suitability for any purpose.
Based on contributions by Steve Cleary, Beman Dawes, Howard Hinnant and John Maddock.
Maintained by John Maddock, the latest version of this file can be found at www.boost.org, and the boost discussion list at www.yahoogroups.com/list/boosttt.
последняя правка: 19.05.2005
библиотека BOOST C++
http://www.boost.org
перевод
Elijah Koziev
www.solarix.ru