Using an explicit template argument to represent a template function’s return type works well when we want to let the user determine the return type. In other cases, requiring an explicit template argument imposes a burden on the user with no compensating advantage. For example, we might want to write a function that takes a pair of iterators denoting a sequence and returns a reference to an element in the sequence:
template <typename It>
??? &fcn(It beg, It end)
{
// process the range
return *beg; // return a reference to an element from the range
}
We don’t know the exact type we want to return, but we do know that we want that type to be a reference to the element type of the sequence we’re processing:
vector<int> vi = {1,2,3,4,5};
Blob<string> ca = { "hi", "bye" };
auto &i = fcn(vi.begin(), vi.end()); // fcn should return int&
auto &s = fcn(ca.begin(), ca.end()); // fcn should return string&
Here, we know that our function will return *beg
, and we know that we can use decltype(*beg)
to obtain the type of that expression. However, beg
doesn’t exist until the parameter list has been seen. To define this function, we must use a trailing return type (§ 6.3.3, p. 229). Because a trailing return appears after the parameter list, it can use the function’s parameters:
// a trailing return lets us declare the return type after the parameter list is seen
template <typename It>
auto fcn(It beg, It end) -> decltype(*beg)
{
// process the range
return *beg; // return a reference to an element from the range
}
Here we’ve told the compiler that fcn
’s return type is the same as the type returned by dereferencing its beg
parameter. The dereference operator returns an lvalue (§ 4.1.1, p. 136), so the type deduced by decltype
is a reference to the type of the element that beg
denotes. Thus, if fcn
is called on a sequence of string
s, the return type will be string&
. If the sequence is int
, the return will be int&
.
Sometimes we do not have direct access to the type that we need. For example, we might want to write a function similar to fcn
that returns an element by value (§ 6.3.2, p. 224), rather than a reference to an element.
The problem we face in writing this function is that we know almost nothing about the types we’re passed. In this function, the only operations we know we can use are iterator operations, and there are no iterator operations that yield elements (as opposed to references to elements).
To obtain the element type, we can use a library type transformation template. These templates are defined in the type_traits
header. In general the classes in type_traits
are used for so-called template metaprogramming, a topic that is beyond the scope of this Primer. However, the type transformation templates are useful in ordinary programming as well. These templates are described in Table 16.1 and we’ll see how they are implemented in § 16.5 (p. 710).
In this case, we can use remove_reference
to obtain the element type. The remove_reference
template has one template type parameter and a (public)
type member named type
. If we instantiate remove_reference
with a reference type, then type
will be the referred-to type. For example, if we instantiate remove_reference<int&>
, the type
member will be int
. Similarly, if we instantiate remove_reference<string&>, type
will be string
, and so on. More generally, given that beg
is an iterator:
remove_reference<decltype(*beg)>::type
will be the type of the element to which beg
refers: decltype(*beg)
returns the reference type of the element type. remove_reference::type
strips off the reference, leaving the element type itself.
Using remove_reference
and a trailing return with decltype
, we can write our function to return a copy of an element’s value:
// must use typename to use a type member of a template parameter; see § 16.1.3 (p. 670)
template <typename It>
auto fcn2(It beg, It end) ->
typename remove_reference<decltype(*beg)>::type
{
// process the range
return *beg; // return a copy of an element from the range
}
Note that type
is member of a class that depends on a template parameter. As a result, we must use typename
in the declaration of the return type to tell the compiler that type
represents a type (§ 16.1.3, p. 670).
Each of the type transformation templates described in Table 16.1 works similarly to remove_reference
. Each template has a public
member named type
that represents a type. That type may be related to the template’s own template type parameter in a way that is indicated by the template’s name. If it is not possible (or not necessary) to transform the template’s parameter, the type
member is the template parameter type itself. For example, if T
is a pointer type, then remove_pointer<T>::type
is the type to which T
points. If T
isn’t a pointer, then no transformation is needed. In this case, type
is the same type as T
.
Exercise 16.40: Is the following function legal? If not, why not? If it is legal, what, if any, are the restrictions on the argument type(s) that can be passed, and what is the return type?
template <typename It>
auto fcn3(It beg, It end) -> decltype(*beg + 0)
{
// process the range
return *beg; // return a copy of an element from the range
}
Exercise 16.41: Write a version of sum
with a return type that is guaranteed to be large enough to hold the result of the addition.