Channels ▼
RSS

C++ Made Easier: Naming Unknown Types


February 2002/C++ Made Easier


Introduction

It is usually easy to figure out the type of an expression in a C++ program. For example, suppose we have defined vi to be a vector of integers:

vector<int> vi;

Then we know that vi[i] has type int, and similarly for other expressions that involve vi.

More generally, if we write a template function that we know takes a vector<T>, such as:

template<class T>
void process(const vector<T>& v)
{
     // ...
}

and we refer to v[i] in this function, we know that v[i] has type T. Although we do not know the specific type of v[i] — this knowledge is impossible, as it depends on T, which we do not know — we can express the type of v[i] in terms of T.

It may be surprising, but there are times when we cannot even know enough about an expression’s type to express it in terms of types that we do know. For example, suppose we modify our process function slightly:

template<class T>
void process(T& v)
{
     // ...
}

Now, instead of insisting that v be a vector of some kind, we are allowing it to be any type at all. If we refer to v[i] in this revised process function, we cannot know anything about the type of v[i] until we have inspected the definition of the type to which T is bound during template instantiation. In other words, it is not until we write a program that calls process that it is possible even to know the type that corresponds to T, which means that in general we cannot know that type at the time we define process itself.

In such a context, how can we give a name to the type of v[i]? We might wish to do so, for example, in order to copy v[i] to a local variable of the same type:

??? vi = v[i];

Here, we need to figure out what to put in place of the ??? to give vi the same type as v[i].

There is no known solution to problems such as this one that is totally satisfactory, so we shall not propose one. Instead, we will talk about an idea for a language extension that has been around for a while, as well as two solutions that are usable now.

The language extension is called typeof and would let us write something such as:

typeof(v[i]) vi = v[i];

Of course, as an extension, it does not presently exist.

One solution that does already exist is to use an auxiliary template function; the other is to rely on programming conventions, such as those that are part of the standard library. Each of these solutions has advantages and disadvantages, which we shall now discuss.

The typeof Solution

C++ already has a pseudo-operator named sizeof, which takes a type or an expression as its argument and yields an integer that represents the size of the object of that type (if the operand is a type), or of the type of the object that the expression would have if evaluated (if the operand is an expression). We refer to sizeof as a pseudo-operator to distinguish it from true operators, such as *, that take only expressions as arguments. A useful peculiarity of sizeof is that it does not actually evaluate the expression. It doesn’t need to do so, because the size of an object depends only on the object’s type. Therefore, the implementation of sizeof needs only to determine the expression’s type, which does not require evaluating the expression.

It is tempting to imagine a similar pseudo-operator named typeof, which would yield the type of its argument expression. Unlike sizeof, typeof would represent a type, not a value. For example, typeof(3) would be equivalent to int, and we could write:

typeof(3) x;

to mean the same as:

int x;

It is clear that such a facility would be useful. We have already seen that we could use it to solve our problem by writing:

typeof(v[i]) vi = v[i];

Moreover, there are some implementations of C++ that already support typeof. Why not consider it a universal solution? There are three reasons.

The first reason is that typeof is not presently part of Standard C++, hence it is not universally available. Therefore, a programmer who uses typeof is limited to using one of the compilers that supports it.

The second reason is that using typeof often requires writing the same expression twice, as we did with v[i] in our most recent example. It is generally wise to avoid programming constructs that require the programmer to write the same thing twice, because it is altogether too easy to write them differently by mistake.

The third reason is that in order to use typeof, we have to be able to write an expression that represents the type we want, and it may not always be possible to write such an expression. For example, if f is a function, and we know that f takes a single argument, there is no way to write an expression that represents the type of that argument.

In short, we cannot be sure that typeof is available, using it often invites subtle programming errors, and there are some problems that it cannot solve. Perhaps we should turn our attention to another solution.

Auxiliary Templates

Suppose we wish to know the type of v[i] so that we can exchange it with v[j]. For example, we might wish to write something like this:

typeof(v[i]) vi = v[i];
v[i] = v[j];
v[j] = vi;

When we see this example, we should realize almost immediately that there is already a way to accomplish the same goal:

std::swap(v[i], v[j]);

How is it that the swap library function can accomplish what we cannot?

The answer is important: whenever we pass an expression as an argument to a template parameter, we can give a name to that parameter’s type. So, for example, the implementation of swap might look something like this:

template<class T>
void swap(T x, T y)
{
     T temp = x;
     x = y;
     y = t;
}

Within the body of this swap function, T is the name for the type of x and y (both of which had better have the same type). More generally, when we need to be able to name the type of an expression, we can do so by creating an appropriate template function and passing that expression as an argument.

Moreover, sometimes we can use this technique to overcome one of the problems with typeof, by revealing more about the type of an expression than typeof would make possible. For example, suppose we know that f is a function with one argument, and we wish to give a name to the type of that argument. Then we might write a template function that looks like this:

template<class Arg, class Res>
void decompose_function(Res (*fp)(Arg))
{
     // ...
}

We define fp as a pointer to a function rather than a function because whenever we pass a function as an argument to another function, what is passed is the address of the argument function rather than the function itself. If we now call decompose_function(f), then inside the body of decompose_function, Arg will be the argument type of f and Res will be f’s result type.

This technique of using an auxiliary template function has two powerful advantages over typeof. First, it exists already in every C++ implementation that supports templates. Second, it makes it possible to extract information that typeof does not supply.

On the other hand, the technique has some disadvantages over typeof as well. It requires that the expression actually be evaluated, as it is hard to see how to pass an expression as an argument to a function without evaluating it. Moreover, because the name given to a type is always a template parameter, it does not persist beyond the body of the function in which it is defined. Therefore, we cannot use this technique to define a local variable with a type that depends on the type of an expression such as v[i]. The best we can do is define a new function and use the type of v[i] to determine the type of a variable in that function. Finally, defining an auxiliary function is relatively verbose, hence error-prone.

To avoid these disadvantages, we turn to programming conventions.

Library Type Conventions

Let’s look again at that expression v[i]. What is v? Suppose we know that v is a standard-library container that has type T. Then we can take advantage of the fact that the library defines, as part of the definition of that container, a type named value_type that is a name for the type of a container element. In other words, we can write:

T::value_type vi = v[i];     <a href="#1">[1]</a>

It might appear on the surface that being able to use value_type in this way relies on v being a standard-library container. However, all it really requires is for the author of T to define an appropriate type named value_type as a member of T. In effect, we can compensate for the nonexistence of typeof and get around the disadvantages of having to define a local template function, if the authors of classes that behave like standard containers observe the same conventions as the standard containers themselves. These conventions include the requirement to define a type named value_type to denote the type of a container element, types named iterator and const_iterator to denote appropriate iterator types for the container, and size_type to denote an unsigned integral type sufficient to hold the number of elements in the container.

A powerful advantage of such conventions over other ways of determining unknown types is that they can provide information that is difficult to obtain otherwise. For example, if T is the type of a container, then T::iterator is the type of an iterator that corresponds to T. If we had typeof available, then we could use typeof(T.begin()) to obtain the same information — but doing so requires the additional knowledge that begin yields an iterator. Although this knowledge is available in this particular case, it is not available in general. In other words, type conventions such as the ones in the standard library are a more general technique than either typeof or defining auxiliary templates, because the only limits on the information available from such conventions are in the conventions themselves.

On the other hand, any convention has the potential problem that programmers might not use it consistently. It is easy to imagine a type that looks a lot like a standard container but does not follow all of the container conventions. It is up to programmers who wish their classes to be as useful as possible to learn about the contexts in which those classes might be used.

Summary

Whenever we write a template function, there is the possibility of writing expressions with types that are not known until our program is compiled. This article has considered three ways of giving names to such types, each of which has advantages and disadvantages.

If we have access to a compiler that supports typeof, and we are willing to write nonstandard programs, then we can use typeof. However, we can often use auxiliary template functions to solve problems that we might otherwise use typeof to solve — and sometimes even to solve some problems that typeof cannot solve.

If we are willing to insist that the classes we use must follow conventions similar to those in the standard library, we can make our work even easier. It is true that then our programs depend on following the conventions, but such dependence is not much different from any other dependence we might have on the correctness of the programs that we use.

If we are defining classes that do not fit the standard library’s conventions, we can define our own conventions to allow our users to obtain appropriate type information as needed. Such conventions are not particularly difficult to define or use; the hardest part of using them is to remember to do it in the first place.

Note

[1] If T is a template parameter, we have to write

typename T::value_type vi=v[i];

because the compiler has no way of knowing that T::value_type is the name of a type until it knows the identity of T.

Andrew Koenig is a member of the Large-Scale Programming Research Department at AT&T’s Shannon Laboratory, and the Project Editor of the C++ standards committee. A programmer for more than 30 years, 15 of them in C++, he has published more than 150 articles about C++ and speaks on the topic worldwide. He is the author of C Traps and Pitfalls and co-author of Ruminations on C++.

Barbara E. Moo is an independent consultant with 20 years’ experience in the software field. During her nearly 15 years at AT&T, she worked on one of the first commercial projects ever written in C++, managed the company’s first C++ compiler project, and directed the development of AT&T’s award-winning WorldNet Internet service business. She is co-author of Ruminations on C++ and lectures worldwide.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video