Provide a meta-function for determining an optimal forwarding type.
More...
Namespaces |
namespace | bslmf |
Detailed Description
- Outline
-
-
- Purpose:
- Provide a meta-function for determining an optimal forwarding type.
-
- Classes:
-
- See also:
- Component bslmf_removecvq
-
- Description:
- This component, while not deprecated, is largely **superseded** by the simpler
bslmf_forwardingreftype
. Although there may be use cases for which bslmf_forwardingtype
is to be prefered, new users are encouraged to consider bslmf_forwardingreftype
and understand the differences between the two components (see Comparison to bslmf_forwardingreftype
).
- This component provides a meta function,
bslmf::ForwardingType
, determining the most efficient forwarding type for a given template type t_TYPE
. The forwarding type is used to pass an argument from the client of a component through a chain of nested function calls to the ultimate consumer of the argument. This component also provides a utility class template, bslmf::ForwardingTypeUtil
, supplying functions to most efficiently forward an argument to another function.
- For instance, basic types (e.g., fundamental types, pointer types, function references and pointers) can efficiently be passed by value down a chain of nested function calls. However a large object or one with a non-trivial copy constructor would be better passed by const reference, even if the ultimate consumer takes that argument by value.
- Another form of optimization is the early decay of arrays to pointers, preventing a proliferation of different template instantiations for every array size being used. Although the outermost function may still be instantiated on the full array type, intermediate functions are all instantiated on the same pointer type, regardless of array size. This decay also applies to reference-to-array types. The user can recover the original array type when forwarding to the final consumer by using
bslmf::ForwardingTypeUtil<T>forwardToTarget()
(see below).
- An argument
v
of type T
can be passed as type ForwardingType<T>Type
down an arbitrarily-long chain of function calls without ever calling std::forward
. However, in order to avoid an extra copy as well as to select the correct overload and instantiation of the eventual target function, it should be converted back to a type that more closely resembles the original T
by calling ForwardingTypeUtil<T>forwardToTarget(v)
.
- This component is intended to be used when performance is of highest concern or when creating function wrappers that are intended to minimize perturbations on the interface of the functions that they wrap.
- Note that previous versions of this component forwarded const references to basic types by value instead of by const reference. This transformation was an attempt to avoid an extra dereference operation, but in real use cases the extra dereference happened anyway, on the call to the outermost forwarding function. Moreover, such a transformation is subtly broken because, in the rare case where the target function cares about the address of the reference (e.g., if it compares it to some known address), it would wind up with the address of a temporary copy, rather than the address of the original argument. Thus, the current component forwards references as references in all cases, including for basic types, except in the case of arrays and functions (that decay to pointers).
-
- Comparison to bslmf_forwardingreftype:
- The components
bslmf_forwardingtype
and bslmf_forwardingreftype
serve the same purpose but have small behavioral differences. In general, we recommend bslmf_forwardingreftype
(the new component) in most contexts.
- Most notably,
bslmf::ForwardingType
(the older class) forwards fundamental and pointer types by value, where as bslmf::ForwardingRefType
will forward fundamental and pointer types by const-reference. For example, bslmf::ForwardingType<int>Type
is int
where as bslmf::ForwardingRefType<int>Type
is const int&
. This applies to fundamental types, pointer types (including member-pointer types), and enum types (which we'll collectively call "basic types"). Forwarding these basic types by value was a performance optimization (and in some rare circumstances was hack needed by older compilers), which predated the standardization of many of the places where bslmf::ForwardingType
was used (function and bind components in particular). The optimzation (potentially) being that passing an int
by value is more likely to be done through a register, where as passing by reference is more likely to require de-referencing memory. Forwarding the types by const-reference, as the newer bslmf::ForwardingRefType
does', is generally simpler and more in line with the modern C++ standard. Using bslmf::ForwardingRefType
avoids some awkward edge cases at the expense of a possible optimization in parameter passing.
-
- Usage:
- In this section we show intended use of this component.
-
- Example 1: Direct Look at Metafunction Results:
- In this example, we invoke
ForwardingType
on a variety of types and look at the resulting Type
member: struct MyType {};
typedef MyType& MyTypeRef;
void main()
{
typedef int T1;
typedef int& T2;
typedef const volatile double& T3;
typedef const double & T4;
typedef const float * & T5;
typedef const float * const & T6;
typedef MyType T7;
typedef const MyType& T8;
typedef MyType& T9;
typedef MyType *T10;
typedef int T11[];
typedef int T12[3];
typedef int EXP1;
typedef int& EXP2;
typedef const volatile double& EXP3;
typedef const double & EXP4;
typedef const float * & EXP5;
typedef const float * const & EXP6;
typedef const MyType& EXP7;
typedef const MyType& EXP8;
typedef MyType& EXP9;
typedef MyType *EXP10;
typedef int *EXP11;
typedef int *EXP12;
assert((bsl::is_same<bslmf::ForwardingType<T1>::Type, EXP1>::value));
assert((bsl::is_same<bslmf::ForwardingType<T2>::Type, EXP2>::value));
assert((bsl::is_same<bslmf::ForwardingType<T3>::Type, EXP3>::value));
assert((bsl::is_same<bslmf::ForwardingType<T4>::Type, EXP4>::value));
assert((bsl::is_same<bslmf::ForwardingType<T5>::Type, EXP5>::value));
assert((bsl::is_same<bslmf::ForwardingType<T6>::Type, EXP6>::value));
assert((bsl::is_same<bslmf::ForwardingType<T7>::Type, EXP7>::value));
assert((bsl::is_same<bslmf::ForwardingType<T8>::Type, EXP8>::value));
assert((bsl::is_same<bslmf::ForwardingType<T9>::Type, EXP9>::value));
assert((bsl::is_same<bslmf::ForwardingType<T10>::Type, EXP10>::value));
assert((bsl::is_same<bslmf::ForwardingType<T11>::Type, EXP11>::value));
assert((bsl::is_same<bslmf::ForwardingType<T12>::Type, EXP12>::value));
}
-
- Example 2: A Logging Invocation Wrapper:
- This example illustrates the use of
ForwardingType
to efficiently implement a wrapper class that holds a function pointer and logs information about each call to the pointed-to-function through the wrapper. Suppose the pointed-to-function takes three arguments whose types are specified via template arguments, where the first argument is required to be convertible to int
.
- First we create a wrapper class that holds a function pointer of the desired type:
template <class PROTOTYPE>
class LoggingWrapper;
template <class RET, class ARG1, class ARG2, class ARG3>
class LoggingWrapper<RET(ARG1, ARG2, ARG3)> {
RET (*d_function_p)(ARG1, ARG2, ARG3);
public:
explicit LoggingWrapper(RET (*function_p)(ARG1, ARG2, ARG3))
: d_function_p(function_p) { }
Then, we declare an overload of the function-call operator that actually invokes the wrapped function. In order to avoid excessive copies of pass-by-value arguments, we use ForwardingType
to declare a more efficient intermediate argument type to forward to the wrapped function pointer: Next, we define logging functions that simply count the number of invocations and number of returns from invocations (e.g., to count how may invocations completed without exiting via exceptions): int invocations = 0, returns = 0;
void logInvocation(int ) { ++invocations; }
void logReturn(int ) { ++returns; }
Now, we implement operator()
to call the logging functions, either side of calling the logged function through the wrapped pointer. To reconstitute the arguments to the function as close as possible to the types they were passed in as, we call the forwardToTarget
member of ForwardingTypeUtil
: Then, in order to see this wrapper in action, we must define a function we wish to wrap. This function will take an argument of type ArgType
that holds an integer value
and keeps track of whether it has been directly constructed or copied from anther ArgType
object. If it has been copied, it keeps track of how many "generations" of copy were made: class ArgType {
int d_value;
int d_copies;
public:
explicit ArgType(int v = 0) : d_value(v), d_copies(0) { }
ArgType(const ArgType& original)
: d_value(original.d_value)
, d_copies(original.d_copies + 1)
{ }
int copies() const { return d_copies; }
int value() const { return d_value; }
};
int myFunc(const short& i, ArgType& x, ArgType y)
{
assert(i == y.copies());
x = y;
return x.value();
}
Finally, we create a instance of LoggingWrapper
to wrap myFunc
, and we invoke it. Note that y
is copied into the second argument of operator()
and is copied again when myFunc
is invoked. However, it is not copied when operator()
calls invoke()
because the ForwardType
of ArgType
is const ArgType&
, which does not create another copy. In C++11, if ArgType
had a move constructor, then the number of copies would be only 1, since the final forwarding would be a move instead of a copy. void usageExample2()
{
ArgType x(0);
ArgType y(99);
LoggingWrapper<int(const short&, ArgType&, ArgType)> lw(myFunc);
assert(0 == invocations && 0 == returns);
lw(1, x, y);
assert(1 == invocations && 1 == returns);
assert(99 == x.value());
}