[/ / Copyright (c) 2012 Marshall Clow / Copyright (c) 2021, Alan Freitas / / Distributed under the Boost Software License, Version 1.0. (See accompanying / file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) /] [/===============] [section:call_traits Call Traits] [/===============] [section Introduction] All of the contents of [@../../../../boost/call_traits.hpp ``] are defined inside `namespace boost`. The template class __call_traits_T__ encapsulates the "best" method to pass a parameter of some type `T` to or from a function, and consists of a collection of `typedef`s defined as in the table below. The purpose of __call_traits__ is to ensure that problems like [link utility.utilities.call_traits.examples.refs "references to references"] never occur, and that parameters are passed in the most efficient manner possible, as in the [link utility.utilities.call_traits.examples examples]. In each case, if your existing practice is to use the type defined on the left, then replace it with the __call_traits__ defined type on the right. Note that for compilers that do not support either partial specialization or member templates, no benefit will occur from using __call_traits__: the __call_traits__ defined types will always be the same as the existing practice in this case. In addition if only member templates and not partial template specialisation is support by the compiler (for example Visual C++ 6) then __call_traits__ cannot be used with array types, although it can still be used to solve the reference to reference problem. [table __call_traits__ types [[Existing practice] [__call_traits__ equivalent] [Description] [Notes]] [ [`T` (return by value) ] [ __call_traits_T__`::value_type` ] [ Defines a type that represents the "value" of type `T`. Use this for functions that return by value, or possibly for stored values of type `T`. ] [2] ] [ [`T&` (return value) ] [ __call_traits_T__`::reference` ] [ Defines a type that represents a reference to type `T`. Use for functions that would normally return a `T&`. ] [1] ] [ [`const T&` (return value) ] [ __call_traits_T__`::const_reference` ] [ Defines a type that represents a constant reference to type `T`. Use for functions that would normally return a `const T&`. ] [1] ] [ [`const T&` (function parameter) ] [ __call_traits_T__`::param_type` ] [ Defines a type that represents the "best" way to pass a parameter of type `T` to a function. ] [1,3] ] ] Notes: # If `T` is already reference type, then __call_traits__ is defined such that [link utility.utilities.call_traits.examples.refs "references to references"] do not occur (requires partial specialization). # If `T` is an array type, then __call_traits__ defines `value_type` as a "constant pointer to type" rather than an "array of type" (requires partial specialization). Note that if you are using `value_type` as a stored value then this will result in storing a "constant pointer to an array" rather than the array itself. This may or may not be a good thing depending upon what you actually need (in other words take care!). # If `T` is a small built in type or a pointer, then `param_type` is defined as `T const`, instead of `T const&`. This can improve the ability of the compiler to optimize loops in the body of the function if they depend upon the passed parameter, the semantics of the passed parameter is otherwise unchanged (requires partial specialization). [endsect] [section Copy constructibility] The following table defines which __call_traits__ types can always be copy-constructed from which other types: [table Which __call_traits__ types can always be copy-constructed from which other types [[] [To `T`] [To `value_type`] [To `reference`] [To `const_reference`] [To `param_type`]] [[From `T`] [iff `T` is copy constructible] [iff `T` is copy constructible] [Yes] [Yes] [Yes]] [[From `value_type`] [iff `T` is copy constructible] [iff `T` is copy constructible] [No] [No] [Yes]] [[From `reference`] [iff `T` is copy constructible] [iff `T` is copy constructible] [Yes] [Yes] [Yes]] [[From `const_reference`] [iff `T` is copy constructible] [No] [No] [Yes] [Yes]] [[From `param_type`] [iff `T` is copy constructible] [iff `T` is copy constructible] [No] [No] [Yes]] ] If `T` is an assignable type the following assignments are possible: [table Which __call_traits__ types are assignable from which other types [[] [To `T`] [To `value_type`] [To `reference`] [To `const_reference`] [To `param_type`]] [[From `T`] [Yes] [Yes] [-] [-] [-]] [[From `value_type`] [Yes] [Yes] [-] [-] [-]] [[From `reference`] [Yes] [Yes] [-] [-] [-]] [[From `const_reference`] [Yes] [Yes] [-] [-] [-]] [[From `param_type`] [Yes] [Yes] [-] [-] [-]] ] [endsect] [section:examples Examples] The following table shows the effect that __call_traits__ has on various types. [table Examples of __call_traits__ types [[] [__call_traits__::`value_type`] [__call_traits__::`reference`] [__call_traits__::`const_reference`] [__call_traits__::`param_type`] [Applies to:]] [[From `my_class`] [`my_class`] [`my_class&`] [`const my_class&`] [`my_class const&`] [All user-defined types]] [[From `int`] [`int`] [`int&`] [`const int&`] [`int const`] [All small built-in types]] [[From `int*`] [`int*`] [`int*&`] [`int* const &`] [`int* const`] [All pointer types]] [[From `int&`] [`int&`] [`int&`] [`const int&`] [`int&`] [All reference types]] [[From `const int&`] [`const int&`] [`const int&`] [`const int&`] [`const int&`] [All constant reference types]] [[From `int[3]`] [`const int*`] [`int(&)[3]`] [`const int(&)[3]`] [`const int* const`] [All array types]] [[From `const int[3]`] [`const int*`] [`const int(&)[3]`] [`const int(&)[3]`] [`const int* const`] [All constant array types]] ] The table assumes the compiler supports partial specialization: if it does not then all types behave in the same way as the entry for "`my_class`", and __call_traits__ can not be used with reference or array types. [section Example 1:] The following class is a trivial class that stores some type `T` by value (see the [@../../../test/call_traits_test.cpp `call_traits_test.cpp`] file). The aim is to illustrate how each of the available __call_traits__ `typedef`s may be used: ``` template 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__::param_type param_type; typedef typename __boost_call_traits__::reference reference; typedef typename __boost_call_traits__::const_reference const_reference; typedef T value_type; typedef typename __boost_call_traits__::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){} }; ``` [endsect] [section:refs Example 2 (the reference to reference problem):] Consider the definition of __std_binder1st__: ``` template class binder1st : public __std_unary_function__ { 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; }; ``` Now consider what happens in the relatively common case that the functor takes its second argument as a reference, that implies that `Operation::second_argument_type` is a reference type, `operator()` will now end up taking a reference to a reference as an argument, and that is not currently legal. The solution here is to modify `operator()` to use __call_traits__: ``` typename Operation::result_type operator()(typename __call_traits__::param_type x) const; ``` Now in the case that `Operation::second_argument_type` is a reference type, the argument is passed as a reference, and the no "reference to reference" occurs. [endsect] [section:example3 Example 3 (the `make_pair` problem):] If we pass the name of an array as one (or both) arguments to `__std_make_pair__`, then template argument deduction deduces the passed parameter as "const reference to array of `T`", this also applies to string literals (which are really array literals). Consequently instead of returning a pair of pointers, it tries to return a pair of arrays, and since an array type is not copy-constructible the code fails to compile. One solution is to explicitly cast the arguments to __std_make_pair__ to pointers, but __call_traits__ provides a better automatic solution that works safely even in generic code where the cast might do the wrong thing: ``` template __std_pair__< typename __boost_call_traits__::value_type, typename __boost_call_traits__::value_type> make_pair(const T1& t1, const T2& t2) { return __std_pair__< typename __boost_call_traits__::value_type, typename __boost_call_traits__::value_type>(t1, t2); } ``` Here, the deduced argument types will be automatically degraded to pointers if the deduced types are arrays, similar situations occur in the standard binders and adapters: in principle in any function that "wraps" a temporary whose type is deduced. Note that the function arguments to __std_make_pair__ are not expressed in terms of __call_traits__: doing so would prevent template argument deduction from functioning. [endsect] [section:example4 Example 4 (optimising fill):] The __call_traits__ template will "optimize" the passing of a small built-in type as a function parameter. This mainly has an effect when the parameter is used within a loop body. In the following example (see [@boost:/libs/type_traits/examples/fill_example.cpp `fill_example.cpp`]), a version of __std_fill__ is optimized in two ways: if the type passed is a single byte built-in type then __std_memset__ is used to effect the fill, otherwise a conventional C++ implementation is used, but with the passed parameter "optimized" using __call_traits__: ``` template struct filler { template static void do_fill(I first, I last, typename __boost_call_traits__::param_type val) { while(first != last) { *first = val; ++first; } } }; template <> struct filler { template static void do_fill(I first, I last, T val) { __std_memset__(first, val, last-first); } }; template inline void fill(I first, I last, const T& val) { enum { can_opt = boost::is_pointer::value && boost::is_arithmetic::value && (sizeof(T) == 1) }; typedef filler filler_t; filler_t::template do_fill(first, last, val); } ``` The reason that this is "optimal" for small built-in types is that with the value passed as `T const` instead of `const T&` the compiler is able to tell both that the value is constant and that it is free of aliases. With this information the compiler is able to cache the passed value in a register, unroll the loop, or use explicitly parallel instructions: if any of these are supported. Exactly how much mileage you will get from this depends upon your compiler - we could really use some accurate benchmarking software as part of boost for cases like this. Note that the function arguments to fill are not expressed in terms of __call_traits__: doing so would prevent template argument deduction from functioning. Instead fill acts as a "thin wrapper" that is there to perform template argument deduction, the compiler will optimise away the call to fill all together, replacing it with the call to `filler<>::do_fill`, which does use __call_traits__. [endsect] [endsect] [section Rationale] The following notes are intended to briefly describe the rationale behind choices made in __call_traits__. All user-defined types follow "existing practice" and need no comment. Small built-in types, what the standard calls [@https://en.cppreference.com/w/cpp/language/types fundamental types], differ from existing practice only in the `param_type` `typedef`. In this case passing `T const` is compatible with existing practice, but may improve performance in some cases (see [link utility.utilities.call_traits.examples.example4 Example 4]). In any case this should never be any worse than existing practice. Pointers follow the same rationale as small built-in types. For reference types the rationale follows [link utility.utilities.call_traits.examples.refs Example 2] - references to references are not allowed, so the __call_traits__ members must be defined such that these problems do not occur. There is a proposal to modify the language such that "a reference to a reference is a reference" (issue #106, submitted by Bjarne Stroustrup). __call_traits_T__`::value_type` and __call_traits_T__`::param_type` both provide the same effect as that proposal, without the need for a language change. In other words, it's a workaround. For array types, a function that takes an array as an argument will degrade the array type to a pointer type: this means that the type of the actual parameter is different from its declared type, something that can cause endless problems in template code that relies on the declared type of a parameter. For example: ``` template struct A { void foo(T t); }; ``` In this case if we instantiate `A` then the declared type of the parameter passed to member function `foo` is `int[2]`, but its actual type is `const int*`. If we try to use the type `T` within the function body, then there is a strong likelihood that our code will not compile: ``` template void A::foo(T t) { T dup(t); // doesn't compile for case that T is an array. } ``` By using __call_traits__ the degradation from array to pointer is explicit, and the type of the parameter is the same as it's declared type: ``` template struct A { void foo(typename __call_traits__::value_type t); }; template void A::foo(typename __call_traits__::value_type t) { typename __call_traits__::value_type dup(t); // OK even if T is an array type. } ``` For `value_type` (return by value), again only a pointer may be returned, not a copy of the whole array, and again __call_traits__ makes the degradation explicit. The `value_type` member is useful whenever an array must be explicitly degraded to a pointer - [link utility.utilities.call_traits.examples.example3 Example 3] provides the test case. Footnote: the array specialisation for __call_traits__ is the least well understood of all the __call_traits__ specialisations. If the given semantics cause specific problems for you, or does not solve a particular array-related problem, then I would be interested to hear about it. Most people though will probably never need to use this specialisation. [endsect] [/===============] [xinclude tmp/call_traits_reference.xml] [/===============] [endsect]