Making sense of STL <utility>
The code starts harmlessly enough with a bunch of boilerplate:
// utility standard header
#pragma once
#ifndef _UTILITY_
#define _UTILITY_
#ifndef RC_INVOKED
#include <xstddef>
#include <iosfwd>
#include <type_traits>
#pragma pack(push,_CRT_PACKING)
#pragma warning(push,3)
#pragma push_macro("new")
#undef new
#pragma warning(disable: 4180 4512)
_STD_BEGIN
But let's not start sloppy, let's look at it in some detail:
#pragma once
should normally be sufficient to stop the compiler from loading this header more than once, but - probably for compatibility reasons - they also add the#ifndef unique-header-define
idiom_CRT_PACKING
defaults to 8. I haven't found the definition for this macro, so I assume it is probably built-in.- I didn't know about
#pragma push_macro
, but apparently what it does is this: in case anyone redefined the new operator with a macro (for example, to add debugging information), push that macro-definition on an internal compiler stack and go back to the "native" (un#defined
) new - Compiler warning 4180 is about 'qualifier applied to function type has no meaning; ignored'. This can happen in templates, because they may blindly apply qualifiers that are then ignored.
- Compiler warning 4512 is when the assignment operator cannot be generated for a class (or template), which may be fine for certain templates
- You can probably guess
_STD_BEGIN
probably is a macro fornamespace std {
, and indeed it is.
So let's dive into each template as it comes along:
// TEMPLATE FUNCTION iter_swap (from <xutility>)
template<class _Ty> inline
void swap(_Ty&, _Ty&)
_NOEXCEPT_OP(is_nothrow_move_constructible<_Ty>::value
&& is_nothrow_move_assignable<_Ty>::value);
My powers of psychic debugging tell me that the author of the STL files - unlike me - is a TAB fan (not a blanks guy), and that her default indent is probably not mine (aka 4).
Note: Because of this, for the rest of the header I will take the liberty of reformatting the code to make it more readable (I think). I will not change any text: I will just change indentation and newlines. Any other change is off-limits.
So, let's start again, this time with the "beautified" version:
template
<class _Ty>
inline
void swap(_Ty&, _Ty&)
_NOEXCEPT_OP(
is_nothrow_move_constructible<_Ty>::value &&
is_nothrow_move_assignable<_Ty>::value);
Actually, we can see that this is not a template, it is a template forward declaration. For now, we will ignore it because everything that makes this
template look strange (the _NOEXCEPT_OP
, the type traits and so on) is explained better
in actual templates as we go along.
Swaping iterators
template
<class _FwdIt1, class _FwdIt2>
inline
void iter_swap(_FwdIt1 _Left, _FwdIt2 _Right)
{
// swap *_Left and *_Right
swap(*_Left, *_Right);
}
OK, our first template! Let's see how far we can dissect this:
- If you call iter_swap, you want to swap two elements pointed at by the two function arguments. And oh hey, <irony>there is even a very useful comment re-stating what you see</irony>. One must assume this passes as humor for STL developers...
- Also note that this template is declared inline.
One question should be: why not declare it like this:
template
<class _FwdIt1, class _FwdIt2>
inline
void iter_swap_bad_idea(<font color="#FF0000">_FwdIt1*</font> _Left, <font color="#FF0000">_FwdIt2*</font> _Right)
{
// swap *_Left and *_Right
swap(*_Left, *_Right);
}
Can you see the problem? It will fail for anything that behaves like a pointer, but is not a pointer. Case in point:
std::shared_ptr<int> a;
std::shared_ptr<int> b;
std::iter_swap(a, b); // works perfectly
iter_swap_bad_idea(a, b); // fails
works with the above (correct) implemenation, but not with the faulty one: because a
is a pointer-like object, but not a pointer.
The above (correct) template implementation assumes the minimal requirement for the caller:
the caller only has to support the *
dereferencing operator, nothing else.
Templates really are a way to support duck-typing in C++.
At this point in life one can safely assume that the authors of the STL knew what they were doing, so let's also spend a moment on thinking about why they wrote:
template<<font color="#FF0000">class</font> _FwdIt1, <font color="#FF0000">class</font> _FwdIt2> inline
and not
template<<font color="#FF0000">typename</font> _FwdIt1, <font color="#FF0000">typename</font> _FwdIt2> inline
It turns out there is an actual reason: class is preferable if you could have template-template parameters. So, you do need to know arcane implementation details even notable C++ guru Stan Lippman isn't 100% sure of (read both links in sequence to see what I mean) when you want to become a STL author later in life.
You can see something else from all of this: A style guide for C++ templates must of essence be different than a style guide for C++ classes
- Normally, my gut instinct would be to use
typename
rather thanclass
for template parameters, because the former is new, and supposedly expresses intent more clearly. But that instinct would be wrong, even if only in some remote instances. - Normally, I would want to
convey my meaning to the reader in the most effective way possible, and that would be
iter_swap_bad_idea
, but actually it is not the most flexible solution, and with templates, flexibilty trumps readability - Normally, you'd want to have lots of comments, especially around the edge cases. But presumably STL writers think in terms of minimizing code size, so as to do a micro-optimiziation for compile times. (And let's not even talk about TDD. Have you ever seen STL test cases?)
OK, on to the next one!
Swaping stuff
// TEMPLATE FUNCTION swap
template
<class _Ty, size_t _Size>
inline
void swap(_Ty (&_Left)[_Size], _Ty (&_Right)[_Size])
_NOEXCEPT_OP(_NOEXCEPT_OP(swap(*_Left, *_Right)))
{
// exchange arrays stored at _Left and _Right
if (&_Left != &_Right)
{
// worth swapping, swap ranges
_Ty *_First1 = _Left;
_Ty *_Last1 = _First1 + _Size;
_Ty *_First2 = _Right;
for (; _First1 != _Last1; ++_First1, ++_First2)
{
_STD iter_swap(_First1, _First2);
}
}
}
First of, let's remove _NOEXCEPT_OP
. This is defined in yvals.h
like this:
#define _NOEXCEPT_OP(x)
We can safely remove it, right? Well, yes and no. Yes, if you assume the above macro definition. But really the problem is that Visual Studio 2013 does not support conditional noexcept specifications, as documented, but presumably the next version will. And when it (finally) does, I assume the macro will be redefined thus:
#define _NOEXCEPT_OP(x) noexcept(x)
And suddenly, it will have an impact: so No, we cannot completely remove it.
Also, you should stop and wonder why they essentially write
noexcept(noexcept(x))
when one would think that a single noexcept
would have done just as well.
But alas: one would be wrong: noexcept
really is two quite different things with the same name:
- the
noexpect
operator performs a compile-time check that returnstrue
if an expression is declared to not throw any exceptions. - the
noexpect
specifier specifies whether a function will throw or not, and may be used by compilers to perform certain optimizations: and you call it withnoexcept(bool)
So the inner noexcept
is an operator, and the outer one is a specifier.
Note: We are only few lines into utility
and already I feel Linus
has been right once again.
- We have alread seen some ugly code.
- We have already seen compilers half-assed implemenation of the language (Here is a life pro tip: forget about C++ compatibility across compiler vendors or often even compiler versions).
- We have already seen improvements that turned out to be bad in other areas (typename).
- And now we see
a completely useless name overloading that (at some point in the future, when the compiler finally understands it) will result in a statement like this:
noexcept(noexcept(swap(*_Left, *_Right)))
which is a bat ugly way of telling the compiler that if itemized
swap
doesn't throw, neither will this array-swap
- So we're doing micro-optimizations, telling the compiler things that other languages' compilers figure out themselves - this on top of already overly complex compilers.
- And we haven't even seen the bad parts yet
But OK, I've started this article (and some hours ago I was foolish enough to think I can do this for more than one header! we shall see!), so let's keep on doing this. Since VS2013 doesn't support noexcept, we'll remove it and look at it again:
template
<class _Ty, size_t _Size>
inline
void swap(_Ty (&_Left)[_Size], _Ty (&_Right)[_Size])
{
// exchange arrays stored at _Left and _Right
if (&_Left != &_Right)
{
// worth swapping, swap ranges
_Ty *_First1 = _Left;
_Ty *_Last1 = _First1 + _Size;
_Ty *_First2 = _Right;
for (; _First1 != _Last1; ++_First1, ++_First2)
{
_STD iter_swap(_First1, _First2);
}
}
}
Again, it should be immediately obvious that _STD
is a helpful macro expanding to std::
. Actually, upon checking it it expands
to ::std::
, so this is a way of
- saving two keystrokes (because you need a blank after _STD its actual keystroke-size is 5, not 4 :)), and
- a paranoid way of stating that you're doing something in the std namespace. The crazy thing is: iter_swap is declared right above this template. I know, stupid me, but I cannot think of a way how you could fool the compiler into not using your iter_swap here.
One other thing: the author is declaring an explicit array size, here:
void swap(_Ty (&_Left)[_Size], _Ty (&_Right)[_Size])
But at least the Visual Studio 2013 compiler (with all warnings enabled) happily ignores this spec. You can do this without any problem:
void xtest(int a[20])
{
...
}
...
int a[3];
xtest(a);
and it won't so much as emit a beep. This also was my understanding based on discussions of array decaying. But OK, maybe this is meant as a hint to those unfortunate souls that try to understand the template headers.
The rest of the macro is pretty standard: if the two array pointers (really: pointers only!) are different, then there is a loop: enumerate the array using pointers, and swap
the contents of the array (using iter_swap
from above). One should mention a couple of potential issues with this function:
- This code does not check whether the pointers are valid. OK, not checking pointers is standard STL policy: you should just know that if you pass garbage in, you'll get garbage out.
- Also, this code doesn't check if the arrays overlap, or indeed the array size makes any sense. Again, standard STL policy.
OK, the next template is really a variation on the swap theme, so let's look at it:
template
<class _Ty>
inline
void swap(_Ty& _Left, _Ty& _Right)
_NOEXCEPT_OP(
is_nothrow_move_constructible<_Ty>::value &&
is_nothrow_move_assignable<_Ty>::value)
{
// exchange values stored at _Left and _Right
_Ty _Tmp = _Move(_Left);
_Left = _Move(_Right);
_Right = _Move(_Tmp);
}
Let's look at the _NOEXCEPT
bits first. It is supposed to say the following
(even though we know it doesn't with Visual Studio 2013):
noexcept(
is_nothrow_move_constructible<_Ty>::value
&&
is_nothrow_move_assignable<_Ty>::value
)
It is easy to understand the intent of this specification:
is_nothrow_move_constructible<_Ty>::value
checks if the type_Ty
has a move-constructoris_nothrow_move_assignable<_Ty>::value
checks if the type_Ty
has a move-assignment operator- If both is true, then this template doesn't throw
So let's look at the body of the function:
// exchange values stored at _Left and _Right
_Ty _Tmp = _Move(_Left);
_Left = _Move(_Right);
_Right = _Move(_Tmp);
Aha! This is probably a C++11-style "moving-swap". But what is this _Move
? Well, if you try the Visual Studio browse information, you'll end up nowhere fast: it'll misleadingly point
you to xutility
, but what you're looking for really is defined in type_traits
:
// TEMPLATE FUNCTION _Move
template
<class _Ty>
inline
typename remove_reference<_Ty>::type&& _Move(_Ty&& _Arg)
_NOEXCEPT
{
// forward _Arg as movable
return ((typename remove_reference<_Ty>::type&&)_Arg);
}
OK, this is definitely a C++11 beast. First of, look at this:
#define _NOEXCEPT throw ()
Basically this tells us that the Microsoft compiler doesn't really support noexcept and instead falls back to the throw ()
hack.
Second, going back to the template, the sole input parameter is _Ty&&
, which (kudos to Effective Modern C++) is
actually a universal reference. There is
a nice discussion of this code on stackoverflow.com that explains how it works - probably better than I ever could.
So take a minute and read the comment.
Having understood that we can see that the swap
template above was basically a swap using C++11-style move operations (if possible), or
copy operations (if necessary). Good! Next one:
template
<class _Ty>
inline
void _Swap_adl(_Ty& _Left, _Ty& _Right)
{
// exchange values stored at _Left and _Right, using ADL
swap(_Left, _Right);
}
Phew! We are few lines in a first simple STL header and already we've seen the std::move
magic, and now this awe inspiring comment:
// exchange values stored at _Left and _Right, using ADL
ADL is Argument Dependant Lookup and a description about it can be read here.
The executive summary here would be this: The compiler will choose the best fit among different overloaded swap
implementations based on the arguments passed at runtime.
The obvious question here should be: why? Why on earth do I need this template?
Wouldn't it be better to use plain and simple swap
rather than the ugly-duckling _Swap_adl
?
The above disussion on ADL notes something that could have an impact here; let me quote it:
ADL is the reason behind the established idiom for swapping two objects in generic code:because callingusing std::swap; swap(obj1, obj2);std::swap(obj1, obj2)
directly would not consider the user-definedswap()
functions that could be defined in the same namespace as the types ofobj1
orobj2
, and just calling the unqualifiedswap(obj1, obj2)
would call nothing if no user-defined overload was provided. In particular,std::iter_swap
and all other standard library algorithms use this approach when dealing with Swappable types.
OK, so I see why you wouldn't want to write std::swap
, but a) you didn't, and b) shouldn't plain old swap
be OK?
I admit I don't know a convincing reason. Normally, I would argue this is just bad code, but we have already seen that the STL authors know their shit, so presumably there is a reason. If you, dear reader, happen to have a good idea, please contact me...
Piecewise construction and you
Next up: another mystery:
// STRUCT piecewise_construct_t
struct piecewise_construct_t
{
// tag type for pair tuple arguments
};
const piecewise_construct_t piecewise_construct = piecewise_construct_t();
OK, this looks fairly stupid. But apparently piecewise_construct is an actual, official thing used to do, guess what, piecewise construction. I am glad that other people question the use-case behind this as well, so I am not alone.
Actually, what we're seeing here is a typical template programming pattern:
- declare an empty struct for the sake of generating a unique type. Note that this is a different intent than one would have in standard C++ code: normally, structs (and by extension classes) are declared for OOP: to merge data + code, or maybe only for code, or only for data, but not "for nothing". But here we don't want any code: we want just a unique type for the compiler.
- the line
declares
const piecewise_construct_t piecewise_construct = piecewise_construct_t();
std::piecewise_construct
as a const (shared) reference to that unique type - later in the code we'll see a use of this combining overloading: if this special type is passed to a function (or constructor), the code knows it has a special semantic meaning.
Trouble comes in pairs
OK, so far we've fooled around with the easy bits, but slowly but surely we're working our way up to std::pair
.
We start with a template forward declaration for tuple
// TEMPLATE STRUCT pair
template
<class...>
class tuple;
So far, so good. Next one:
template
<class _Ty1, class _Ty2>
struct pair
{
// store a pair of values
typedef pair<_Ty1, _Ty2> _Myt;
typedef _Ty1 first_type;
typedef _Ty2 second_type;
OK, the template starts harmlessly enough, with a couple of typedefs. Interestingly enough, std::pair
is a template
struct, not a class, and the only reason I can guess for this is that it makes everything public:
by default.
Also, my guess would be that _Myt
is there only
to safe a few keystrokes; first_type
and second_type
are standard defines that exist there as standard names.
pair()
: first(), second()
{
// default construct
}
The default constructor initializes the two values to their defaults. Actually, let's discuss this a bit. Let's look at a simple class:
class foo
{
public:
foo()
{}
int this_is_the_name_of_a_variable_NOT_its_type;
};
If you want to initialize this_is_the_name_of_a_variable_NOT_its_type
to its default value, you can of course use this:
...
foo() : this_is_the_name_of_a_variable_NOT_its_type(0)
{}
..
But (and I admit I didn't know this before), you can also do this:
...
foo() : this_is_the_name_of_a_variable_NOT_its_type()
{}
..
This will initialize this_is_the_name_of_a_variable_NOT_its_type
to its default value - which, for int
, happens to be 0, but for other objects might be
something else. The really strange thing here is we are using the variable name as a type, something that I was not aware
you could do anywhere else. Really, it's like using the default constructor: but normally to specify that we would use the type, not the name...right?
...
foo() : this_is_the_name_of_a_variable_NOT_its_type(int())
{}
..
So now we have three methods of setting an int to zero. Note that this is a special treatment of the constructor: in normal code, you can use both of these:
this_is_the_name_of_a_variable_NOT_its_type = 0;
this_is_the_name_of_a_variable_NOT_its_type = int();
but you cannot use this:
this_is_the_name_of_a_variable_NOT_its_type = this_is_the_name_of_a_variable_NOT_its_type();
Because, duh, this_is_the_name_of_a_variable_NOT_its_type
is the name of a variable, NOT its type
Back to std::pair
. Here is our next constructor:
pair(const _Ty1& _Val1, const _Ty2& _Val2)
: first(_Val1), second(_Val2)
{
// construct from specified values
}
This looks like a pretty harmless constructor that uses two lvalue references to initialize both values. In a way, it is like two copy-constructors in a row.
template
<class _Other1,
class _Other2,
class = typename enable_if<is_convertible<const _Other1&, _Ty1>::value &&
is_convertible<const _Other2&, _Ty2>::value,
void>::type>
pair(const pair<_Other1, _Other2>& _Right)
: first(_Right.first), second(_Right.second)
{
// construct from compatible pair
}
The intent of this constructor is pretty obvious: if _Other1
is convertible to _Ty1
, and
_Other2
is convertible to _Ty2
, then this constructor applies. But let's check how that actually works. We start with this expression:
class = typename enable_if<
is_convertible<const _Other1&, _Ty1>::value &&
is_convertible<const _Other2&, _Ty2>::value, void>::type
First of all, this is an unnamed template parameter. That means, the code doesn't actually use it. The expression after the =
assigns a default value to that parameter, so nobody has to specify it either. Sounds strange? It gets better:
The enable_if
code (actually, it is a template of sorts, but I would be hard pressed to find the exact name: is it a function? a method? a template? a struct? But I digress) What happens here is that the compiler either generates a valid type, or it fails to do so:
- If it generates a valid type, then this template has a default value, so only the first two parameters are considered when resolving the correct constructor overload.
- If it fails to do so, the third template parameter is undefined, so for those types the constructor will not be considered.
The definition of enable_if
is in xtr1common
, and with a little bit of reformatting it looks like this:
template
<bool _Test, class _Ty = void>
struct enable_if
{
// type is undefined for assumed !_Test
};
So the core template does not define enable_if::type
(which is another way of saying: if the compiler
resolves the default template, it will not see the expression enable_if::type
, so there won't be a valid default value for the above constructor, so it won't be a candidate for name resolution). But there is more, a template specialisation for true
:
template
<class _Ty>
struct enable_if<true, _Ty>
{
// type is _Ty for _Test
typedef _Ty type;
};
This is template metaprogramming indeed, and it is worth doing a short recap because it is another one of those standard patterns for templates:
- Have a unique name (for example,
enable_if::type
). - Define it conditionally: have a default template that does not specify the type, but specialisations that do.
- The type is like a sort of "Schrödingers Cat" for template programming: it may exist, and it may not: you cannot know at the time of writing another template
- Now, you have a list of overloaded templates from which you want to select conditionally: just use this type as one of the defaults. If the type exists (i.e. the condition is met at compile-time), then your template may become a possible candidate, otherwise it won't.
I have to admit: there is one thing I don't understand about this: why the default parameter type void
, when
the user of the macro also passes void
? I am talking about this:
template
<bool _Test<font color="#FF0000">, class _Ty = void</font>>
struct enable_if
and
class = typename enable_if<
is_convertible<const _Other1&, _Ty1>::value &&
is_convertible<const _Other2&, _Ty2>::value<font color="#FF0000">, void</font>>::type
If you happen to know a good answer, again, please contact me.
But we're not done yet. Looking back at our expression we see that we haven't looked at is_convertible
yet, and this is
defined in type_traits
like this:
template
<class _From, class _To>
struct is_convertible _IS_CONVERTIBLE(_From, _To)
{
// determine whether _From is convertible to _To
};
This doesn't tell us a lot. In particular, about the only thing one could infer from looking at this is that _IS_CONVERTIBLE
is probably
a macro, and it probably specifies a base-class for this struct. Let's look it up:
#define _IS_CONVERTIBLE(_From, _To) \
: _Cat_base<__is_convertible_to(_From, _To)>
OK, what is this? The header comment tells us:
// COMPILER SUPPORT MACROS
// VC++ V14 SUPPORT
and indeed, Google is our friend: This is one of many Microsoft
compiler extension for Type Traits: and it does what the name indicates: tells us if _From
is convertible to _To
.
template
<class _Other1,
class _Other2,
class = typename enable_if<is_convertible<const _Other1&, _Ty1>::value &&
is_convertible<const _Other2&, _Ty2>::value,
void>::type>
pair(const pair<_Other1, _Other2>& _Right)
: first(_Right.first), second(_Right.second)
{
// construct from compatible pair
}
We can summarize now how this works: If both template arguments are convertible to the base types of the tuple, then this constructor is a possible match. And in essence it is very similar to the 'copy-constructor-like' constructor we saw before. The only bit of magic really is using type traits to determine at compile time if this is a potential match.
Next up is the assignment operator, and that is pretty basic:
template
<class _Other1, class _Other2>
_Myt& operator=(const pair<_Other1, _Other2>& _Right)
{
// assign from compatible pair
first = _Right.first;
second = _Right.second;
return (*this);
}
This looks pretty boring by now. I want to point out one particular thing anyway: often, you are instructed to do a self-assignment test before doing the actual copy, somewhat like this:
template
<class _Other1, class _Other2>
pair& operator=(const pair<_Other1, _Other2>& _Right)
{
<font color="#FF0000">if( this != &_Right )
{</font>
// assign from compatible pair
first = _Right.first;
second = _Right.second;
return (*this);
<font color="#FF0000">}</font>
}
But, this code more-or-less assumes that child values are safe with regards of self-assignment (as they always should) and therefor skips this as unnecessary.
Next up: our first variadic template:
template<class _Tuple1,
class _Tuple2,
size_t... _Indexes1,
size_t... _Indexes2> inline
pair(_Tuple1& _Val1,
_Tuple2& _Val2,
_Arg_idx<_Indexes1...>,
_Arg_idx<_Indexes2...>);
But it turns out to be an enormous anti-climax: this isn't really a template, it is another template forward declaration. So we'll dissect it when we find it in another header. Basically the same thing applies to the next template:
template<class... _Types1,
class... _Types2> inline
pair(piecewise_construct_t,
tuple<_Types1...> _Val1,
tuple<_Types2...> _Val2)
_NOEXCEPT_OP((is_nothrow_constructible<_Ty1, _Types1&&...>::value
&& is_nothrow_constructible<_Ty2, _Types2&&...>::value));
Again, this is only a forward declaration, not the actual fun part. OK, let's look at the next one:
template
<class _Other1,
class _Other2,
class = typename enable_if<
is_convertible<_Other1, _Ty1>::value &&
is_convertible<_Other2, _Ty2>::value, void>::type>
pair(_Other1&& _Val1, _Other2&& _Val2)
_NOEXCEPT_OP((
is_nothrow_constructible<_Ty1, _Other1&&>::value&&
is_nothrow_constructible<_Ty2, _Other2&&>::value))
: first(_STD forward<_Other1>(_Val1)),
second(_STD forward<_Other2>(_Val2))
{
// construct from moved values
}
OK, this is a template, but most of it should come in pretty natural by now.
class = typename enable_if<
is_convertible<_Other1, _Ty1>::value &&
is_convertible<_Other2, _Ty2>::value, void>::type>
This is once again the template-overloading magic of enable_if
, just like with the extended copy-constructor from
above.
_NOEXCEPT_OP((
is_nothrow_constructible<_Ty1, _Other1&&>::value&&
is_nothrow_constructible<_Ty2, _Other2&&>::value))
by now poses only a very small mystery: what is is_nothrow_constructible<_Ty1, _Other1&&>::value
: it is another one of the many Microsoft
compiler extension for Type Traits that basically says: return a valid (true) type if _Ty1
can be
constructed from a universal _Other1
reference.
But here is something new:
: first(_STD forward<_Other1>(_Val1)),
second(_STD forward<_Other2>(_Val2))
This is a use of the C++11 std::forward
: "perfectly forwarding" of _Val1
to the constructor of first
. What it effectivelymeans is this:
- Should you pass a
_Other1
lvalue (for example, a reference to an object), then the copy constructor will be called forfirst
- Should you pass a
_Other1
rvalue (anything else, in particular the actual object), then the move constructor will be called (assuming of course thatfirst
does have a move constructor.
Mysteries solved, on to the next one:
template
<class _Other1,
class _Other2,
class = typename enable_if<
is_convertible<_Other1, _Ty1>::value &&
is_convertible<_Other2, _Ty2>::value, void>::type>
pair(pair<_Other1, _Other2>&& _Right)
_NOEXCEPT_OP((is_nothrow_constructible<_Ty1, _Other1&&>::value &&
is_nothrow_constructible<_Ty2, _Other2&&>::value))
: first(_STD forward<_Other1>(_Right.first)),
second(_STD forward<_Other2>(_Right.second))
{
// construct from moved compatible pair
}
This is basically the same as the previous constructor, with a small exception: the previous one had this parameter list:
pair(pair<_Other1, _Other2>&& _Right)
so you basically call it with two arguments, one for first
and one for right
, whereas this constructor
is called for a std::pair
instance:
pair(pair<_Other1, _Other2>&& _Right)
I think you'll probably agree that having come so far the code in utility
has lost lots of its threat-potential...
But ok, there are more templates to come, let's check them:
template
<class _Other1, class _Other2>
_Myt& operator=(pair<_Other1, _Other2>&& _Right)
_NOEXCEPT_OP((is_nothrow_assignable<_Ty1, _Other1&&>::value &&
is_nothrow_assignable<_Ty2, _Other2&&>::value))
{
// assign from moved compatible pair
first = _STD forward<_Other1>(_Right.first);
second = _STD forward<_Other2>(_Right.second);
return (*this);
}
This is a move-assigment operator. It doesn't throw if moving the callers types doesn't throw. The only thing causing a slight unease while reading this code
should be this: it uses std::forward
rather than std::move
to move the parameters. Once again this is a question I was unable to answer
to my own satisfaction: if you know the answer, please contact me.
_Myt& operator=(_Myt&& _Right)
_NOEXCEPT_OP((is_nothrow_move_assignable<_Ty1>::value &&
is_nothrow_move_assignable<_Ty2>::value))
{
// assign from moved pair
first = _STD forward<_Ty1>(_Right.first);
second = _STD forward<_Ty2>(_Right.second);
return (*this);
}
Hm. Reading this one questions why this exists at all. Personally I think the template code would have sufficed, and just when I thought I had finally understood a bit of templates, the STL authors repeat the code for no reason I can discern. How is this different from the move-assignment operator template immediately before it?. You know the drill: if you know the answer, please contact me.
void swap(_Myt& _Right)
_NOEXCEPT_OP( _NOEXCEPT_OP(_Swap_adl(this->first, _Right.first)) &&
_NOEXCEPT_OP(_Swap_adl(this->second, _Right.second)))
{
// exchange contents with _Right
if (this != &_Right)
{
// different, worth swapping
_Swap_adl(first, _Right.first);
_Swap_adl(second, _Right.second);
}
}
So we meet again, strange _Swap_adl
friend. First, let's see what we can easily know about
this function, and then look at its mysteries.
- This is a
swap()
method on thestd::pair
- It is used to swap an instance with another instance (specified as reference, as one would expect)
- It doesn't throw an exception, if swapping both first and second doesn't throw an exception
But none of this provides an obvious motivation for _Swap_adl
over plain old swap
,
so I will keep this point open as well.
_Myt& operator=(const _Myt& _Right)
{
// assign from copied pair
first = _Right.first;
second = _Right.second;
return (*this);
}
This is an assignment operator. Again, like we've seen with the assignment operator template earlier, there is no checking for self-assignment. But this also brings up the question we've already seen with the template move-assignment and the explicit move assignment: why have both, when they do precisely the same?
The class ends with some rather boring bits:
_Ty1 first; // the first stored value
_Ty2 second; // the second stored value
};
Pair template functions
template
<class _Ty1, class _Ty2>
inline
void swap(pair<_Ty1, _Ty2>& _Left, pair<_Ty1, _Ty2>& _Right)
_NOEXCEPT_OP(_NOEXCEPT_OP(_Left.swap(_Right)))
{
// swap _Left and _Right pairs
_Left.swap(_Right);
}
I mentioned it above already, this is duck typing: you can think of this as a specialisation of the std::swap
template: for any two std::pair
s, call the swap()
member function to swap the contents.
template
<class _Ty1, class _Ty2>
inline
bool operator==(const pair<_Ty1, _Ty2>& _Left, const pair<_Ty1, _Ty2>& _Right)
{
// test for pair equality
return (_Left.first == _Right.first && _Left.second == _Right.second);
}
This is an overloaded ==
operator that, given two pairs, will test whether they are equal: they are equal if both
first
and second
members are equal
template
<class _Ty1, class _Ty2>
inline
bool operator!=(const pair<_Ty1, _Ty2>& _Left, const pair<_Ty1, _Ty2>& _Right)
{
// test for pair inequality
return (!(_Left == _Right));
}
Duh. Two std::pairs
are not equal if they are ... surprise, surprise: not equals.
template
<class _Ty1, class _Ty2>
inline
bool operator<(const pair<_Ty1, _Ty2>& _Left, const pair<_Ty1, _Ty2>& _Right)
{
// test if _Left < _Right for pairs
return (_Left.first < _Right.first ||
(!(_Right.first < _Left.first) && _Left.second < _Right.second));
}
This is the <
operator.
- If
left.first
is less thanright.first
,left
is less thanright
. - The second expression deserves a moment of thought: what it wants to say is this:
- If
left.first
is equal toright.first
, then we compareleft.second
andright.second
!< on the right side of an || expression
) it expresses the same thing but using only the<
operator, not the==
operator. So this expression works for objects that define a strict ordering, but don't define (at least explicitly) a==
operator. We are back to template authors trying to assume the minimal functionality in order to express a maximally general solution... - If
template
<class _Ty1, class _Ty2>
inline
bool operator>(const pair<_Ty1, _Ty2>& _Left, const pair<_Ty1, _Ty2>& _Right)
{
// test if _Left > _Right for pairs
return (_Right < _Left);
}
This is another one of those less-is-more tricks: the >
operator is implemented
by swapping argument order and doing <
.
template
<class _Ty1, class _Ty2>
inline
bool operator<=(const pair<_Ty1, _Ty2>& _Left, const pair<_Ty1, _Ty2>& _Right)
{
// test if _Left <= _Right for pairs
return (!(_Right < _Left));
}
Rather obviously, <=
can be expressed as !>
, or as !<
with swapped argument order...
template
<class _Ty1, class _Ty2>
inline
bool operator>=(const pair<_Ty1, _Ty2>& _Left, const pair<_Ty1, _Ty2>& _Right)
{
// test if _Left >= _Right for pairs
return (!(_Left < _Right));
}
The >=
operator is ... !<
// TEMPLATE FUNCTION make_pair
template
<class _Ty1, class _Ty2>
inline
pair<
typename _Unrefwrap<_Ty1>::type,
typename _Unrefwrap<_Ty2>::type>
make_pair(_Ty1&& _Val1, _Ty2&& _Val2)
{
// return pair composed from arguments
typedef pair<typename _Unrefwrap<_Ty1>::type, typename _Unrefwrap<_Ty2>::type> _Mypair;
return (_Mypair(_STD forward<_Ty1>(_Val1), _STD forward<_Ty2>(_Val2)));
}
Just when you thought things were getting too easy the STL comes up with another tricky one.
OK, let's start with the known unknowns: what is _Unrefwrap
? Well, it is defined in type_traits
like this:
// CLASS _Unrefwrap
template <class _Ty> class reference_wrapper;
template <class _Ty> struct _Unrefwrap_helper
{
// leave unchanged if not a reference_wrapper
typedef _Ty type;
};
template <class _Ty> struct _Unrefwrap_helper<reference_wrapper<_Ty>>
{
// make a reference from a reference_wrapper
typedef _Ty& type;
};
template <class _Ty> struct _Unrefwrap
{
// decay, then unwrap a reference_wrapper
typedef typename decay<_Ty>::type _Ty1;
typedef typename _Unrefwrap_helper<_Ty1>::type type;
};
Let me take a moment to assure you, dear reader, that while writing this page I often wanted to wring my hands in despair, and this is one of those moments where you think: may god punish the inventors of templates.
But let's do it in slo-mo
template <class _Ty> class reference_wrapper;
Another forward declaration. It is time to fire up
the trusty documentation:
quote: "std::reference_wrapper
is a class template that wraps a reference in a copyable, assignable object", defined in
xrefwarp
and we're going to cross that bridge when we reach it. For now, we only know what it does and that it actually
exists.
template <class _Ty> struct _Unrefwrap_helper
{
// leave unchanged if not a reference_wrapper
typedef _Ty type;
};
This template defines a new type _Unrefwrap_helper::type
that is really an alias for _Ty
. So when
the compiler calls _Unrefwrap_helper<FOO>::type
, it actually sees the type FOO
. OK. Why bother?
template <class _Ty> struct _Unrefwrap_helper<reference_wrapper<_Ty>>
{
// make a reference from a reference_wrapper
typedef _Ty& type;
};
So now we have a specialisation: if you call _Unrefwrap_helper
with a reference_wrapper
for a type,
you get the underlying type.
And the final bit was this:
template <class _Ty> struct _Unrefwrap
{
// decay, then unwrap a reference_wrapper
typedef typename decay<_Ty>::type _Ty1;
typedef typename _Unrefwrap_helper<_Ty1>::type type;
};
So it decays the template parameter, and then
it even removes the reference if someone is foolish enough to provide a reference_wrapper
rather than an actual,
honest-to-god, reference. It is like more decaying that std::decay
.
But now that we know this, how does it help us with this:
// TEMPLATE FUNCTION make_pair
template
<class _Ty1, class _Ty2>
inline
pair<
typename _Unrefwrap<_Ty1>::type,
typename _Unrefwrap<_Ty2>::type>
make_pair(_Ty1&& _Val1, _Ty2&& _Val2)
{
// return pair composed from arguments
typedef pair<typename _Unrefwrap<_Ty1>::type, typename _Unrefwrap<_Ty2>::type> _Mypair;
return (_Mypair(_STD forward<_Ty1>(_Val1), _STD forward<_Ty2>(_Val2)));
}
Well, let's look at this with examples:
- If the template arguments are plain types, then the result is a tuple of plain types as well. Example:
int a, b; puts(typeid(std::make_pair(a, b)).name()); // -> prints 'struct std::pair<int,int>'
- If the template arguments are references to plain types, the result still is a tuple of plain types. Example:
This is true even if you explicitly specify the template arguments are references:
int& c = a; int& d = b; puts(typeid(std::make_pair(c, d)).name()); // -> prints 'struct std::pair<int,int>'
puts(typeid(std::make_pair≪int&, int&>(c, d)).name()); // -> prints 'struct std::pair<int,int>'
- Only if you are a smart-arse and pass a
std::reference_wrapper
, you get a pair of references:std::reference_wrapper<int> wc(c); std::reference_wrapper<int> wd(d); puts(typeid(std::make_pair(wc, wd)).name()); // -> prints 'struct std::pair<int &,int &>'
The funny thing is this: looking at _Unrefwrap
above, one would have thought that even std::reference_wrapper
should by law be decayed: but it isn't. Let's investigate this:
puts(typeid(std::_Unrefwrap<std::reference_wrapper<int>>::type).name());
// -> prints 'int'
Going back to the actual std::make_pair
macro, it seems that this
typedef pair<typename _Unrefwrap<_Ty1>::type, typename _Unrefwrap<_Ty2>::type> _Mypair;
should, by law and custom, return a std::pair<int,int>
, and the fact that it doesn't looks like a
bug to me. Once again I am stumped: if you, dear reader, know a convincing reason why this is so please... contact me.
Rel-ops land
// TEMPLATE OPERATORS
namespace rel_ops
{
// nested namespace to hide relational operators from std
template
<class _Ty>
inline
bool operator!=(const _Ty& _Left, const _Ty& _Right)
{
// test for inequality, in terms of equality
return (!(_Left == _Right));
}
Ok, now we're in rel_ops
land. This namespace is used to define
standard relational operations like !=
and >
in terms of only two operators: ==
and <
.
We have seen the logic behind this at work already above. What I find funny is that before, we had an explicit specification of these
for std::pair
, and now we define the very same things again, but in a different namespace, as templates. It is hard to
see the logic behind all of this: shouldn't these overloads simply be compiler defaults? why define them in a super-special namespace
std::rel_ops
? I guess this has something to do with history, backward-compatibility and the general tendency of the C++ team to prefer
complicated hacks to sweeping core changes...
But anyway, if you've come so far the following templates should pose no questions at all:
template
<class _Ty>
inline
bool operator>(const _Ty& _Left, const _Ty& _Right)
{
// test if _Left > _Right, in terms of operator<
return (_Right < _Left);
}
template
<class _Ty>
inline
bool operator<=(const _Ty& _Left, const _Ty& _Right)
{
// test if _Left <= _Right, in terms of operator<
return (!(_Right < _Left));
}
template
<class _Ty>
inline
bool operator>=(const _Ty& _Left, const _Ty& _Right)
{
// test if _Left >= _Right, in terms of operator<
return (!(_Left < _Right));
}
}
_STD_END
Sizing up tuples
Tuples are really declared in tuple
and not in utility
, but since this header is a mixed bag anyway,
somebody thought it a brilliant idea to include tuple sizes not alongside the actual tuple declaration, but
somewhere else entirely, namely here:
_STD_BEGIN
// TEMPLATE STRUCT tuple_size
template
<class _Tuple>
struct tuple_size
{
// size of non-tuple
static_assert(_Always_false<_Tuple>::value, "The C++ Standard doesn't define tuple_size for this type.");
};
OK, hold it right there. I can see what this is supposed to do, but let's check if we really get all of it.
One can immediately see that the static_assert
will always be raised by this template. But why does it say
static_assert(_Always_false<_Tuple>::value, ...
static_assert(false, ...
Well, looking at the definition of _Always_false
in xstddef
, one sees this:
template
<class _Ty>
struct _Always_false
{
// false value that probably won't be optimized away
static const bool value = false;
};
Hm. The comment seems to say that this is somehow better than plain-old false
because it won't be optimized away.
But how can that ever occur for a static_assert
? You guessed right: if you, dear reader, have a convincing idea: please contact me.
But back to the actual template: what the default, unspecialized template says is basically this: "sorry, caller, I have no idea what size this tuple is". We'll see if things get more helpful as we get along...
template
<class _Ty1, class _Ty2>
struct tuple_size<pair<_Ty1, _Ty2>>
: integral_constant<size_t, 2>
{
// size of pair
};
OK, what is an integral_constant
? Let's look in xtr1common
, to find this:
// TEMPLATE CLASS integral_constant
template
<class _Ty, _Ty _Val>
struct integral_constant
{
// convenient template for integral constant types
static const _Ty value = _Val;
typedef _Ty value_type;
typedef integral_constant<_Ty, _Val> type;
operator value_type() const
{
// return stored value
return (value);
}
};
I think I'll talk more about this once I get around to the header, but for now one thing should be obvious:
by inheriting from integral_constant<size_t, 2>
, tuple_size<pair<_Ty1, _Ty2>>
inherits a member value
that is 2
. In other words, if you ask for the tuple_size
of a
pair
, you get 2: pairs are tuples of 2.
template
<class... _Types>
struct tuple_size<tuple<_Types...> >
: integral_constant<size_t, sizeof...(_Types)>
{
// size of tuple
};
The first new thing I learnt reading this was that among the many changes brought by the introduction of
variadic templates was also the introduction of the
the new sizeof...
operator, which unsurprisingly returns the sizeof a variadic items list.
Given that, the rest of the template becomes pretty obvious: really all the hard work resides in sizeof...
.
Now for some more fun definitions:
template
<class _Tuple>
struct tuple_size<const _Tuple>
: tuple_size<_Tuple>
{
// size of const tuple
};
template
<class _Tuple>
struct tuple_size<volatile _Tuple>
: tuple_size<_Tuple>
{
// size of volatile tuple
};
template
<class _Tuple>
struct tuple_size<const volatile _Tuple>
: tuple_size<_Tuple>
{
// size of const volatile tuple
};
All of this says that if you have const
, volatile
or const volatile
tuples, the tuple_size
is equal to the one of the plain vanilla object. Duh!
// TUPLE INTERFACE TO pair
template
<size_t _Idx, class _Tuple>
struct tuple_element;
template
<int _Idx, class _Ty>
struct _Pair_data;
OK, so tuple_element
is a forward declaration, but _Pair_data
is a lot more mysterious. It is a forward
declaration alright, but it is also deliberately not defined. Really, I am not kidding you, the general template is not defined, so if you try to use it
with just any old class, you'll get a compile time error:
std::_Pair_data<0, int> test;
// -> fails with error C2079: 'test' uses undefined struct 'std::_Pair_data<0,int>
Worry, not, here are the specialisations that make this undefined template actually useful:
template
<class _Ty1, class _Ty2>
struct _Pair_data<0, pair<_Ty1, _Ty2>>
{
// struct to pick out argument type and value at index 0
typedef typename add_lvalue_reference<const _Ty1>::type _Ctype;
typedef typename add_lvalue_reference<_Ty1>::type _Rtype;
typedef typename add_rvalue_reference<_Ty1>::type _RRtype;
- note this is a template that defines a series of specialized templates (whoa dude!): namely a specialisation of the form
<constant 0, std::pair<two-types>
. So this is possible without a compiler error:std::_Pair_data<0, std::pair<int, int>> test;
- The
_Pair_data<...>::_Ctype
is a lvalue reference to the same type as the firststd::pair
parameter, with const added. - The
_Pair_data<...>::_Rtype
is a lvalue reference to the same type as the firststd::pair
parameter - The
_Pair_data<...>::_RRtype
is a lvalue reference to the same type as the firststd::pair
parameter - If you just saw two identical lines don't worry: things will become more clear soon:
static _Rtype _Val(pair<_Ty1, _Ty2>& _Pr)
{
// return value
return (_Pr.first);
}
static _Ctype _Val(const pair<_Ty1, _Ty2>& _Pr)
{
// return value
return (_Pr.first);
}
static _RRtype _Val(pair<_Ty1, _Ty2>&& _Pr)
{
// return value
return (_STD forward<_Ty1>(_Pr.first));
}
};
So the difference between _Rtype
and _RRtype
is not actually in the type, but in the fact that the compiler
will move std::pair.first
if a rvalue std::pair
is passed to the _Val
static method, and copy the lvalue
otherwise. (And a const lvalue, if the input is const to begin with).
But what does it all mean? Well, this is obviously a helper class. It basically defines a set of overloaded functions
std::_Pair_data<0, std::pair<int, int>>::_Val(...)
that always return the first
item of a std::pair
. Given that, understanding the next template is a piece
of cake:
template
<class _Ty1, class _Ty2>
struct _Pair_data<<font color="#FF0000">1</font>, pair<_Ty1, _Ty2> >
{
// struct to pick out argument type and value at index 1
typedef typename add_lvalue_reference<const _Ty2>::type _Ctype;
typedef typename add_lvalue_reference<_Ty2>::type _Rtype;
typedef typename add_rvalue_reference<_Ty2>::type _RRtype;
static _Rtype _Val(pair<_Ty1, _Ty2>& _Pr)
{
// return value
return (_Pr.second);
}
static _Ctype _Val(const pair<_Ty1, _Ty2>& _Pr)
{
// return value
return (_Pr.second);
}
static _RRtype _Val(pair<_Ty1, _Ty2>&& _Pr)
{
// return value
return (_STD forward<_Ty2>(_Pr.second));
}
};
Really this is more of the same: only if you pass the constant index 1 you'll get the second
item of a std::pair
.
template
<class _Ty1, class _Ty2>
struct tuple_element<0, pair<_Ty1, _Ty2> >
{
// struct to determine type of element 0 in pair
typedef _Ty1 type;
};
template
<class _Ty1, class _Ty2>
struct tuple_element<1, pair<_Ty1, _Ty2> >
{
// struct to determine type of element 1 in pair
typedef _Ty2 type;
};
So this is the actual tuple_element
definition. Actually, it is two specialisations of the template: if you use
the index 0, you'll get the type of std::pair.first
, and for 1 you'll get unsurprisingly std::pair.right
.
But note that these two functions do not refer to _Pair_data
, so why was it defined at all?
template
<int _Idx, class _Ty1, class _Ty2>
inline
typename _Pair_data<_Idx, pair<_Ty1, _Ty2> >::_Rtype
get(pair<_Ty1, _Ty2>& _Pr) _NOEXCEPT
{
// get reference to element at _Idx in pair _Pr
return (_Pair_data<_Idx, pair<_Ty1, _Ty2> >::_Val(_Pr));
}
Aha! If you call std::get
on a std::pair
, the compiler will find this specialisation, and it will use the
_Pair_data::_Val()
method to find the actual element. Note: in a code review you would consider it bad style
to not define this helper struct where you need it, but alas, STL writers have certain freedoms we don't.
Also note that there are two more specialisations coming up
template
<int _Idx, class _Ty1, class _Ty2>
inline
typename _Pair_data<_Idx, pair<_Ty1, _Ty2> >::_Ctype
get(const pair<_Ty1, _Ty2>& _Pr) _NOEXCEPT
{
// get const reference to element at _Idx in pair _Pr
return (_Pair_data<_Idx, pair<_Ty1, _Ty2> >::_Val(_Pr));
}
The use of _Ctype
gives it away: this is the specialisation if you pass in a const pair: you'll get const members back.
template
<int _Idx, class _Ty1, class _Ty2>
inline
typename _Pair_data<_Idx, pair<_Ty1, _Ty2> >::_RRtype
get(pair<_Ty1, _Ty2>&& _Pr) _NOEXCEPT
{
// get rvalue reference to element at _Idx in pair _Pr
typedef typename _Pair_data<_Idx, pair<_Ty1, _Ty2> >::_RRtype _RRtype;
return (_STD forward<_RRtype>(_Pair_data<_Idx, pair<_Ty1, _Ty2> >::_Val(_Pr)));
}
And finally: this is the specialisation for rvalues: if you pass them in, you'll get the rvalue of the member back.
Summary
We have almost reached the end of utility
, only a few lines remain:
_STD_END
_STD_BEGIN
namespace tr1 { // TR1 additions
using _STD get;
using _STD tuple_element;
using _STD tuple_size;
} // namespace tr1
_STD_END
Really just a couple of template forwards back from TR1-stuff which we'll elect not to discuss at this point. The standard end-of-header boilerplate follows:
#pragma pop_macro("new")
#pragma warning(pop)
#pragma pack(pop)
#endif /* RC_INVOKED */
#endif /* _UTILITY_ */
And finally, a few words for our friends, the lawyers:
/*
* This file is derived from software bearing the following
* restrictions:
*
* Copyright (c) 1994
* Hewlett-Packard Company
*
* Permission to use, copy, modify, distribute and sell this
* software and its documentation for any purpose is hereby
* granted without fee, provided that the above copyright notice
* appear in all copies and that both that copyright notice and
* this permission notice appear in supporting documentation.
* Hewlett-Packard Company makes no representations about the
* suitability of this software for any purpose. It is provided
* "as is" without express or implied warranty.
*/
/*
* Copyright (c) 1992-2012 by P.J. Plauger. ALL RIGHTS RESERVED.
* Consult your license regarding permissions and restrictions.
V6.00:0009 */
OK, so this was it: our very first STL header dissected. At this point I am fairly confident that it is indeed possible to read the STL code, if one is dedicated enough. But we have noted a couple of loose ends: things I couldn't quite resolve, and open questions. So let's see what the story will be for our next header... I can barely await it :)