C++ Newsletter/Tutorial Issue 20
Issue #020
February, 1997
Contents
- Partial Specializations of Class Templates
- Partial Ordering of Function Templates
- Notes From ANSI/ISO - More on terminate() and unexpected()
- Notes From ANSI/ISO - Follow-up on Placement New/Delete
- Notes From ANSI/ISO - Current Draft Standard Now Publicly Available
- Introduction to STL Part 7 - Iterators
PARTIAL SPECIALIZATIONS OF CLASS TEMPLATES
In issue #012 we talked about template specialization, for example:
template <class T> class String {
// stuff
};
template <> class String<char> {
// stuff
};
In this example we have a String template, and a "special case" where the type argument to the template is "char".
A relatively new feature (in terms of availability) generalizes this feature a bit, and is known as partial specialization of class templates. For example, in the standard library for C++ there is a template:
template <class T, class Allocator> class vector {
// stuff
};
for managing vectors of objects. A partial specialization of this template might look like:
// primary template
template <class T, class Allocator> class vector {
// stuff
};
// partial specialization
template <class Allocator> class vector<bool, Allocator> {
// stuff
};
One type parameter has been bound to "bool", and the other is still unbound and may be specified by the user (in this example, to specify a type of storage allocator). In this case, the specialization allows for use of a packed representation of the vector.
With partial specializations there is an issue with which template is to be preferred in a given case. For example:
template <class T> class A {};
template <class T> class A<T*> {};
A<double> a1;
A<int*> a2;
In this example, "double" can only match the first template, while "int*" could match either. But "T*" is considered to be more specialized than "T", and so the second template is used for "int*".
There are several additional angles on matching that you may wish to investigate for yourself. Compilers may not yet have this feature. See below for details of where to get a copy of the current draft standard.
PARTIAL ORDERING OF FUNCTION TEMPLATES
Somewhat related to partial class specializations is partial ordering of function templates. Suppose that you have the vector example from the previous section, with one additional feature:
// primary template
template <class T, class Allocator> class vector {
template <class U, class W> friend void f(vector<U, W>);
// stuff
};
// partial specialization
template <class Allocator> class vector<bool, Allocator> {
template <class U> friend void f(vector<bool, U>);
// stuff
};
Without partial ordering of function templates, that is, preferring the friend function "f(vector<bool, U>)" in the specialized case, this example would be ambiguous, and there would be no way to determine which function template to call.
Like the example in the previous section, there are various rules that are applied to order templates and choose the appropriate one. For example:
template <class T> void f(T) {}
template <class T> void f(T*) {}
void g()
{
int* p = 0;
f(12.34); // calls first template
f(p); // calls second one
}
As with class templates, there is a notion of one function template being "more specialized" than another.
NOTES FROM ANSI/ISO - MORE ON TERMINATE() AND UNEXPECTED()
Jonathan Schilling, jls@sco.com
In C++ Newsletter #019 the terminate handler, the unexpected handler, and the standard library function uncaught_exception() were introduced.
The standards committee recently decided what values uncaught_exception() should return when called from these handlers: false from unexpected() and true from terminate().
The latter ruling is somewhat counter-intuitive, because an exception is considered "caught" in the standard when terminate() is called, so logically uncaught_exception() should return the inverse. The rationale for the decision was that uncaught_exception() should include the case where terminate has been called by the implementation. Some committee members argued that it should return false, or that the value should be left undefined. But at the end of the day this is a good example of the kind of minutiae a standards committee must deal with, because if you consider that the purpose of uncaught_exception() is to help keep you out of terminate(), then if you're already in terminate() anyway it pretty much doesn't much matter what it returns.
Note however that these rules only apply when unexpected() and terminate() are called by the implementation. When direct user calls are made to these functions (see again Newsletter #019), uncaught_exception() will return false unless the direct user call was made from code executing as part of an exception. In the case of terminate() this difference between implementation calls and direct calls might complicate simulation testing of error conditions.
NOTES FROM ANSI/ISO - FOLLOW-UP ON PLACEMENT NEW/DELETE
Jonathan Schilling, jls@sco.com
Also introduced in Newsletter #019 were placement new and placement delete. In addition to the language providing this general capability, the C++ standard library also provides a specific instance for void*:
void* operator new(size_t, void*);
void operator delete(void*, void*);
These are accessed by saying:
#include <new>
These functions are defined to do nothing (though new returns its argument). Their purpose is to allow construction of an object at a specific address, which is often useful in embedded systems and other low-level applications:
const unsigned long MEMORY_MAP_IO_AREA = 0xf008;
...
Some_Class* p = new ((void*) MEMORY_MAP_IO_AREA) Some_Class();
Based on a fairly recent decision of the standards committee, this definition of placement new/delete for void* is reserved by the library, and cannot be replaced by the user (unlike the normal global operator new, which can be). The library also defines a similar placement new/delete for allocating arrays at a specific address.
NOTES FROM ANSI/ISO - CURRENT DRAFT STANDARD NOW PUBLICLY AVAILABLE
Jonathan Schilling, jls@sco.com
In C++ Newsletter #018 it was mentioned that the C++ standards committee has recently issued its "second Committee Draft" (CD2) of the standard, with an associated public review period, but that due to ISO policy the draft would not be available without charge.
ISO has just now reversed this policy, and two Web sites now have information on how to download the draft and to make public review comments:
http://www.maths.warwick.ac.uk/c++/pub/
The ANSI public review period ends on March 18, so if you're interested in submitting a comment, better do it quickly!
INTRODUCTION TO STL PART 7 - ITERATORS
In previous issues we've covered various STL container types such as lists and sets. With this issue we'll start discussing iterators. Iterators in STL are mechanisms for accessing data elements in containers and for cycling through lists of elements.
Let's start by looking at an example:
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 100;
void main()
{
int arr[N];
arr[50] = 37;
int* ip = find(arr, arr + N, 37);
if (ip == arr + N)
cout << "item not found in array\n";
else
cout << "found at position " << ip - arr << "\n";
}
In this example, we have a 100-long array of ints, and we want to search for the location in the array where a particular value (37) is stored. To do this, we call find() and specify the starting point ("arr") and ending point ("arr + N") in the array, along with the value to search for (37).
An index is returned to the value in the array, or to one past the end of the array if the value is not found. In this example, "arr", "arr + N", and "ip" are iterators.
This approach works fine, but requires some knowledge of pointer arithmetic in C++. Another approach looks like this:
#include <algorithm>
#include <vector>
#include <iostream>
using namespace std;
const int N = 100;
void main()
{
vector<int> iv(N);
iv[50] = 37;
vector<int>::iterator iter = find(iv.begin(), iv.end(), 37);
if (iter == iv.end())
cout << "not found\n";
else
cout << "found at " << iter - iv.begin() << "\n";
}
This code achieves the same end, but is at a higher level. Instead of an actual array of ints, we have a vector of ints, and vector is a higher-level construct than a primitive C/C++ array. For example, a vector has within in it knowledge of how long it is, so that we can say "iv.end()" to refer to the end of the array, without reference to N.
In future issues we will be looking at several additional examples of iterator usage.
ACKNOWLEDGEMENTS
Thanks to Nathan Myers, Eric Nagler, David Nelson, Jonathan Schilling, and Elaine Siegel for help with proofreading.
SUBSCRIPTION INFORMATION / BACK ISSUES
To subscribe to the newsletter, send mail to majordomo@world.std.com with this line as its message body:
subscribe c_plus_plus
Back issues are available via FTP from:
rmi.net /pub2/glenm/newslett
or on the Web at:
There is also a Java newsletter. To subscribe to it, say:
subscribe java_letter
using the same majordomo@world.std.com address.
-------------------------
Copyright (c) 1997 Glen McCluskey. All Rights Reserved.
This newsletter may be further distributed provided that it is copied in its entirety, including the newsletter number at the top and the copyright and contact information at the bottom.
Glen McCluskey & Associates
Professional C++ Consulting
Internet: glenm@glenmccl.com
Phone: (800) 722-1613 or (970) 490-2462
Fax: (970) 490-2463
FTP: rmi.net /pub2/glenm/newslett (for back issues)
Web: http://rainbow.rmi.net/~glenm