Chapter 13. Future Directions

C++ templates evolved considerably from their initial design in 1988 until the standardization of C++ in 1998 (the technical work was completed in November 1997). After that, the language definition was stable for several years, but during that time various new needs have arisen in the area of C++ templates. Some of these needs are simply a consequence of a desire for more consistency or orthogonality in the language. For example, why wouldn’t default template arguments be allowed on function templates when they are allowed on class templates? Other extensions are prompted by increasingly sophisticated template programming idioms that often stretch the abilities of existing compilers.

In what follows we describe some extensions that have come up more than once among C++ language and compiler designers. Often such extensions were prompted by the designers of various advanced C++ libraries (including the C++ standard library). There is no guarantee that any of these will ever be part of standard C++. On the other hand, some of these are already provided as extensions by certain C++ implementations.

13.1 The Angle Bracket Hack

Among the most common surprises for beginning template programmers is the necessity to add some blank space between consecutive closing angle brackets. For example:

#include <list>
#include <vector>

typedef std::vector<std::list<int> > LineTable;   // OK

typedef std::vector<std::list<int>>  OtherTable;  // SYNTAX ERROR

The second typedef declaration is an error because the two closing angle brackets with no intervening blank space constitute a “right shift” (>>) operator, which makes no sense at that location in the source.

Yet detecting such an error and silently treating the >> operator as two closing angle brackets (a feature sometimes referred to as the angle bracket hack) is relatively simple compared with many of the other capabilities of C++ source code parsers. Indeed, many compilers are already able to recognize such situations and will accept the code with a warning.

Hence, it is likely that a future version of C++ will require the declaration of OtherTable (in the previous example) to be valid. Nevertheless, we should note that there are some subtle corners to the angle bracket hack. Indeed, there are situations when the >> operator is a valid token within a template argument list. The following example illustrates this:

template<int N> class Buf;

template<typename T> void strange() {}
template<int N> void strange() {}

int main()
{
    strange<Buf<16>>2> >();  // the >> token is not an error
}

A somewhat related issue deals with the accidental use of the digraph <:, which is equivalent to the bracket [ (see Section 9.3.1 on page 129). Consider the following code extract:

template<typename T> class List;
class Marker;

List<::Marker>* markers; // ERROR

The last line of this example is treated as List[:Marker>* markers;, which makes no sense at all. However, a compiler could conceivably take into account that a template such as List can never validly be followed by a left bracket and disable the recognition of the corresponding digraph in that context.

13.2 Relaxed typename Rules

Some programmers and language designers find the rules for the use of typename (see Section 5.1 on page 43 and Section 9.3.2 on page 130) too strict. For example, in the following code, the occurrence of typename in typename Array<T>::ElementT is mandatory, but the one in typename Array<int>::ElementT is prohibited (an error):

template <typename T>
class Array {
  public:
    typedef T ElementT;
    
};

template <typename T>
void clear (typename Array<T>::ElementT& p);      // OK

template<>
void clear (typename Array<int>::ElementT& p);    // ERROR

Examples such as this can be surprising, and because it is not difficult for a C++ compiler implementation simply to ignore the extra keyword, the language designers are considering allowing the typename keyword in front of any qualified typename that is not already elaborated with one of the keywords struct, class, union, or enum. Such a decision would probably also clarify when the .template, ->template, and ::template constructs (see Section 9.3.3 on page 132) are permissible.

Ignoring extraneous uses of typename and template is relatively straightforward from an implementer’s point of view. Interestingly, there are also situations when the language currently requires these keywords but when an implementation could do without them. For example, in the previous function template clear(), a compiler can know that the name Array<T>::ElementT cannot be anything but a type name (no expressions are allowed at that point), and therefore the use of typename could be made optional in that situation. The C++ standardization committee is therefore also examining changes that would reduce the number of situations when typename and template are required.

13.3 Default Function Template Arguments

When templates were originally added to the C++ language, explicit function template arguments were not a valid construct. Function template arguments always had to be deducible from the call expression. As a result, there seemed to be no compelling reason to allow default function template arguments because the default would always be overridden by the deduced value.

Since then, however, it is possible to specify explicitly function template arguments that cannot be deduced. Hence, it would be entirely natural to specify default values for those nondeducible template arguments. Consider the following example:

template <typename T1, typename T2 = int>
T2 count (T1 const& x);
class MyInt {
    
};

void test (Container const& c)
{
    int i = count(c);
    MyInt = count<MyInt>(c);
    assert(MyInt == i);
}

In this example, we have respected the constraint that if a template parameter has a default argument value, then each parameter after that must have a default template argument too. This constraint is needed for class templates; otherwise, there would be no way to specify trailing arguments in the general case. The following erroneous code illustrates this:

template <typename T1 = int, typename T2>
class Bad;

Bad<int>* b;  // Is the given int a substitution for T1 or for T2?

For function templates, however, the trailing arguments may be deduced. Hence, there is no technical difficulty in rewriting our example as follows:

template <typename T1 = int, typename T2>
T1 count (T2 const& x);

void test (Container const& c)
{
    int i = count(c);
    MyInt = count<MyInt>(c);
    assert(MyInt == i);
}

At the time of this writing the C++ standardization committee is considering extending function templates in this direction.

In hindsight, programmers have also noted uses that do not involve explicit template arguments. For example:

template <typename T = double>
void f(T const& = T());
int main()
{
    f(1);          // OK: deduce T = int
    f<long>(2);    // OK: T = long; no deduction
    f<char>();     // OK: same as f<char>(’’);
    f();           // Same as f<double>(0.0);
}

Here a default template argument enables a default call argument to apply without explicit template arguments.

13.4 String Literal and Floating-Point Template Arguments

Among the restrictions on nontype template arguments, perhaps the most surprising to beginning and advanced template writers alike is the inability to provide a string literal as a template argument.

The following example seems intuitive enough:

template <char const* msg>
class Diagnoser {
  public:
    void print();
};

int main()
{
    Diagnoser<"Surprise!">().print();
}

However, there are some potential problems. In standard C++, two instances of Diagnoser are the same type if and only if they have the same arguments. In this case the argument is a pointer value—in other words, an address. However, two identical string literals appearing in different source locations are not required to have the same address. We could thus find ourselves in the awkward situation that Diagnoser<"X"> and Diagnoser<"X"> are in fact two different and incompatible types! (Note that the type of "X" is char const[2], but it decays to char const* when passed as a template argument.)

Because of these (and related) considerations, the C++ standard prohibits string literals as arguments to templates. However, some implementations do offer the facility as an extension. They enable this by using the actual string literal contents in the internal representation of the template instance. Although this is clearly feasible, some C++ language commentators feel that a nontype template parameter that can be substituted by a string literal value should be declared differently from one that can be substituted by an address. At the time of this writing, however, no such declaration syntax has received overwhelming support.

We should also note an additional technical wrinkle in this issue. Consider the following template declarations, and let’s assume that the language has been extended to accept string literals as template arguments in this case:

template <char const* str>
class Bracket {
  public:
    static char const* address() const;
    static char const* bytes() const;
};

template <char const* str>
char const* Bracket<T>::address() const
{
    return str;
}

template <char const* str>
char const* Bracket<T>::bytes() const
{
    return str;
}

In the previous code, the two member functions are identical except for their names—a situation that is not that uncommon. Imagine that an implementation would instantiate Bracket<"X"> using a process much like macro expansion: In this case, if the two member functions are instantiated in different translation units, they may return different values. Interestingly, a test of some C++ compilers that currently provide this extension reveals that they do suffer from this surprising behavior.

A related issue is the ability to provide floating-point literals (and simple constant floating-point expressions) as template arguments. For example:

template <double Ratio>
class Converter {
  public:
    static double convert (double val) const {
        return val*Ratio;
    }
};

typedef Converter<0.0254> InchToMeter;

This too is provided by some C++ implementations and presents no serious technical challenges (unlike the string literal arguments).

13.5 Relaxed Matching of Template Template Parameters

A template used to substitute a template template parameter must match that parameter’s list of template parameters exactly. This can sometimes have surprising consequences, as shown in the following example:

#include <list>
    // declares:
    //  namespace std {
    //      template <typename T,
    //                typename Allocator = allocator<T> >
    //      class list;
    // }

template<typename T1,
         typename T2,
         template<typename> class Container>
                   // Container expects templates with only one parameter
class Relation {
  public:
    
  private:
    Container<T1> dom1;
    Container<T2> dom2;
};

int main()
{
    Relation<int, double, std::list> rel;
        // ERROR: std::list has more than one template parameter
    
}

This program is invalid because our template template parameter Container expects a template taking one parameter, whereas std::list has an allocator parameter in addition to its parameter that determines the element type.

However, because std::list has a default template argument for its allocator parameter, it would be possible to specify that Container matches std::list and that each instantiation of Container uses the default template argument of std::list (see Section 8.3.4 on page 112).

An argument in favor of the status quo (no match) is that the same rule applies to matching function types. However, in this case the default arguments cannot always be determined because the value of a function pointer usually isn’t fixed until run time. In contrast, there are no “template pointers,” and all the required information can be available at compile time.

Some C++ compilers already offer the relaxed matching rule as an extension. This issue is also related to the issue of typedef templates (discussed in the next section). Indeed, consider replacing the definition of main() in our previous example with:

template <typename T>
typedef list<T> MyList;

int main()
{
    Relation<int, double, MyList> rel;
}

The typedef template introduces a new template that now exactly matches Container with respect to its parameter list. Whether this strengthens or weakens the case for a relaxed matching rule is, of course, arguable.

This issue has been brought up before the C++ standardization committee, which is currently not inclined to add the relaxed matching rule.

13.6 Typedef Templates

Class templates are often combined in relatively sophisticated ways to obtain other parameterized types. When such parameterized types appear repeatedly in source code, it is natural to want a shortcut for them, just as typedefs provide a shortcut for unparameterized types.

Therefore, C++ language designers are considering a construct that may look as follows:

template <typename T>
typedef vector<list<T> > Table;

After this declaration, Table would be a new template that can be instantiated to become a concrete type definition. Such a template is called a typedef template (as opposed to a class template or a function template). For example:

Table<int> t;        // t has type vector<list<int> >

Currently, the lack of typedef templates is worked around by using member typedefs of class templates. For our example we might use:

template <typename T>
class Table {
  public:
    typedef vector<list<T> > Type;
};

Table<int>::Type t;  // t has type vector<list<int> >

Because typedef templates are to be full-fledged templates, they could be specialized much like class templates:

// primary typedef template:
template<typename T> typedef T Opaque;

// partial specialization:
template<typename T> typedef void* Opaque<T*>;

// full specialization:
template<> typedef bool Opaque<void>;

Typedef templates are not entirely straightforward. For example, it is not clear how they would participate in the deduction process:

void candidate(long);

template<typename T> typedef T DT;

template<typename T> void candidate(DT<T>);

int main()
{
    candidate(42);  // which candidate() should be called?
}

It is not clear that deduction should succeed in this case. Certainly, deduction is not possible with arbitrary typedef patterns.

13.7 Partial Specialization of Function Templates

In Chapter 12 we discussed how class templates can be partially specialized, whereas function templates are simply overloaded. The two mechanisms are somewhat different.

Partial specialization doesn’t introduce a completely new template: It is an extension of an existing template (the primary template). When a class template is looked up, only primary templates are considered at first. If, after the selection of a primary template, it turns out that there is a partial specialization of that template with a template argument pattern that matches that of the instantiation, its definition (in other words, its body) is instantiated instead of the definition of the primary template. (Full template specializations work exactly the same way.)

In contrast, overloaded function templates are separate templates that are completely independent of one another. When selecting which template to instantiate, all the overloaded templates are considered together, and overload resolution attempts to choose one as the best fit. At first this might seem like an adequate alternative, but in practice there are a number of limitations:

• It is possible to specialize member templates of a class without changing the definition of that class. However, adding an overloaded member does require a change in the definition of a class. In many cases this is not an option because we may not own the rights to do so. Furthermore, the C++ standard does not currently allow us to add new templates to the std namespace, but it does allow us to specialize templates from that namespace.

• To overload function templates, their function parameters must differ in some material way. Consider a function template R convert(T const&) where R and T are template parameters. We may very well want to specialize this template for R = void, but this cannot be done using overloading.

• Code that is valid for a nonoverloaded function may no longer be valid when the function is overloaded. Specifically, given two function templates f(T) and g(T) (where T is a template parameter), the expression g(&f<int>) is valid only if f is not overloaded (otherwise, there is no way to decide which f is meant).

• Friend declarations refer to a specific function template or an instantiation of a specific function template. An overloaded version of a function template would not automatically have the privileges granted to the original template.

Together, this list forms a compelling argument in support of a partial specialization construct for function templates.

A natural syntax for partially specializing function templates is the generalization of the class template notation:

template <typename T>
T const& max (T const&, T const&);        // primary template

template <typename T>
T* const& max <T*>(T* const&, T* const&); // partial specialization

Some language designers worry about the interaction of this partial specialization approach with function template overloading. For example:

template <typename T>
void add (T& x, int i);  // a primary template

template <typename T1, typename T2>
void add (T1 a, T2 b);   // another (overloaded) primary template

template <typename T>
void add<T*> (T*&, int); // which primary template does this specialize?

However, we expect such cases would be deemed errors without major impact on the utility of the feature.

At the time of this writing, this extension is under consideration by the C++ standardization committee.

13.8 The typeof Operator

When writing templates, it is often useful to be able to express the type of a template-dependent expression. Perhaps the poster child of this situation is the declaration of an arithmetic operator for a numeric array template in which the element types of the operands are mixed. The following example should make this clear:

template <typename T1, typename T2>
Array<???> operator+ (Array<T1> const& x, Array<T2> const& y);

Presumably, this operator is to produce an array of elements that are the result of adding corresponding elements in the arrays x and y. The type of a resulting element is thus the type of x[0]+y[0]. Unfortunately, C++ does not offer a reliable way to express this type in terms of T1 and T2.

Some compilers provide the typeof operator as an extension that addresses this issue. It is reminiscent of the sizeof operator in that it can take an expression and produce a compile-time entity from it, but in this case the compile-time entity can act as the name of a type. In our previous example this allows us to write:

template <typename T1, typename T2>
Array<typeof(T1()+T2())> operator+ (Array<T1> const& x,
                                    Array<T2> const& y);

This is nice, but not ideal. Indeed, it assumes that the given types can be default-initialized. We can work around this assumption by introducing a helper template as follows:

template <typename T>
T makeT();  // no definition needed

template <typename T1, typename T2>
Array<typeof(makeT<T1>()+makeT<T2>())>
  operator+ (Array<T1> const& x,
             Array<T2> const& y);

We really would prefer to use x and y in the typeof argument, but we cannot do so because they have not been declared at the point of the typeof construct. A radical solution to this problem is to introduce an alternative function declaration syntax that places the return type after the parameter types:

// operator function template:
template <typename T1, typename T2>
operator+ (Array<T1> const& x, Array<T2> const& y)
  -> Array<typeof(x+y)>;

// regular function template:
template <typename T1, typename T2>
function exp(Array<T1> const& x, Array<T2> const& y)
  -> Array<typeof(exp(x, y))>

As the example illustrates, a new keyword (here, function) is necessary to enable the new syntax for nonoperator functions (for operator functions, the operator keyword is sufficient to guide the parsing process).

Note that typeof must be a compile-time operator. In particular, typeof will not take into account covariant return types, as the following example shows:

class Base {
  public:
    virtual Base clone();
};

class Derived : public Base {
  public:
    virtual Derived clone();  // covariant return type
};

void demo (Base* p, Base* q)
{
   typeof(p->clone()) tmp = p->clone();
                              // tmp will always have type Base
   
}

Section 15.2.4 on page 271 shows how promotion traits are sometimes used to partially address the absence of a typeof operator.

13.9 Named Template Arguments

Section 16.1 on page 285 describes a technique that allows us to provide a nondefault template argument for a specific parameter without having to specify other template arguments for which a default value is available. Although it is an interesting technique, it is also clear that it results in a fair amount of work for a relatively simple effect. Hence, providing a language mechanism to name template arguments is a natural thought.

We should note at this point, that a similar extension (sometimes called keyword arguments) was proposed earlier in the C++ standardization process by Roland Hartinger (see Section 6.5.1 of [StroustrupDnE]). Although technically sound, the proposal was ultimately not accepted into the language for various reasons. At this point there is no reason to believe named template arguments will ever make it into the language.

However, for the sake of completeness, we mention one syntactic idea that has floated among certain designers:

template<typename T,
         Move: typename M = defaultMove<T>,
         Copy: typename C = defaultCopy<T>,
         Swap: typename S = defaultSwap<T>,
         Init: typename I = defaultInit<T>,
         Kill: typename K = defaultKill<T> >
class Mutator {
   
};

void test(MatrixList ml)
{
   mySort (ml, Mutator <Matrix, Swap: matrixSwap>);
}

Note how the argument name (preceding a colon) is distinct from the parameter name. This allows us to keep the practice of using short names for the parameters used in the implementation while having a self-documenting name for the argument names. Because this can be overly verbose for some programming styles, one can also imagine the ability to omit the argument name if it is identical to the parameter name:

template<typename T,
         : typename Move = defaultMove<T>,
         : typename Copy = defaultCopy<T>,
         : typename Swap = defaultSwap<T>,
         : typename Init = defaultInit<T>,
         : typename Kill = defaultKill<T> >
class Mutator {
   
};

13.10 Static Properties

In Chapter 15 and Chapter 19 we discuss various ways to categorize types “at compile time.” Such traits are useful in selecting specializations of templates based on the static properties of the type. (See, for example, our CSMtraits class in Section 15.3.2 on page 279, which attempts to select optimal or near-optimal policies to copy, swap, or move elements of the argument type.)

Some language designers have observed that if such “specialization selections” are commonplace, they shouldn’t require elaborate user-defined code if all that is sought is a property that the implementation knows internally anyway. The language could instead provide a number of built-in type traits. The following could be a valid complete C++ program with such an extension:

#include <iostream>

int main()
{
    std::cout << std::type<int>::is_bit_copyable << ’ ’;
    std::cout << std::type<int>::is_union << ’ ’;
}

Although a separate syntax could be developed for such a construct, fitting it in a user-definable syntax may allow for a more smooth transition from the current language to a language that would include such facilities. However, some of the static properties that a C++ compiler can easily provide may not be obtainable using traditional traits techniques (for example, determining whether a type is a union), which is an argument in favor of making this a language element. Another argument is that it can significantly reduce the amount of memory and machine cycles required by a compiler to translate programs that rely on such properties.

13.11 Custom Instantiation Diagnostics

Many templates put some implicit requirements on their parameters. When the arguments of an instantiation of such a template do not fulfill the requirements, either a generic error is issued or the generated instantiation does not function correctly. In early C++ compilers, the generic errors produced during template instantiations were often exceedingly opaque (see page 75 for an example). In more recent compilers, the error messages are sufficiently clear for an experienced programmer to track down a problem quickly, but there is still a desire to improve the situation. Consider the following artificial example (meant to illustrate what happens in real template libraries):

template <typename T>
void clear (T const& p)
{
    *p = 0;  // assumes T is a pointerlike type
}

template <typename T>
void core (T const& p)
{
    clear(p);
}

template <typename T>
void middle (typename T::Index p)
{
    core(p);
}

template <typename T>
void shell (T const& env)
{
    typename T::Index i;
    middle<T>(i);
}

class Client {
  public:
    typedef int Index;
    
};

Client main_client;

int main()
{
    shell(main_client);
}

This example illustrates the typical layering of software development: High-level function templates like shell() rely on components like middle(), which themselves make use of basic facilities like core(). When we instantiate shell(), all the layers below it also need to be instantiated. In this example, a problem is revealed in the deepest layer: core() is instantiated with type int (from the use of Client::Index in middle()) and attempts to dereference a value of that type, which is an error. A good generic diagnostic will include a trace of all the layers that led to the problems, but this amount of information may be unwieldy.

An alternative that has often been proposed is to insert a device in the highest level template to inhibit deeper instantiation if known requirements from lower levels are not satisfied. Various attempts have been made to implement such devices in terms of existing C++ constructs (for example, see [BCCL]), but they are not always effective. Hence, it is not surprising that language extensions have been proposed to address the issue. Such an extension could clearly build on top of the static properties facilities discussed earlier. For example, we can envision modifying the shell() template as follows:

template <typename T>
void shell (T const& env)
{
    std::instantiation_error(
           std::type<T>::has_member_type<"Index">,
           "T must have an Index member type");
    std::instantiation_error(
           !std::type<typename T::Index>::dereferencable,
           "T::Index must be a pointer-like type");
    typename T::Index i;
    middle(i);
}

The instantiation_error() pseudo-function would presumably cause the implementation to abort the instantiation (thereby avoiding the diagnostics triggered by the instantiation of middle()) and cause the compiler to issue the given message.

Although this is feasible, there are some drawbacks to this approach. For example, it can quickly become cumbersome to describe all the properties of a type in this manner. Some have proposed to allow “dummy code” constructs to serve as the condition to abort instantiation. Here is one of the many proposed forms (this one introduces no new keywords):

template <typename T>
void shell (T const& env)
{
    template try {
        typename T::Index p;
        *p = 0;
    } catch "T::Index must be a pointer-like type";
    typename T::Index i;
    middle(i);
}

The idea here is that the body of a template try clause is tentatively instantiated without actually generating object code, and, if an error occurs, the diagnostic that follows is issued. Unfortunately, such a mechanism is hard to implement because even though the generation of code could be inhib-ited, there are other side effects internal to a compiler that are hard to avoid. In other words, this relatively small feature would likely require a considerable reengineering of existing compilation technology.

Most such schemes also have other limitations. For example, many C++ compilers can report diagnostics in different languages (English, German, Japanese, and so forth), but providing various translations in the source code could prove excessive. Furthermore, if the instantiation process is truly aborted and the precondition was not precisely formulated, a programmer might be much worse off than with a generic (albeit unwieldy) diagnostic.

13.12 Overloaded Class Templates

It is entirely possible to imagine that class templates could be overloaded on their template parameters. For example, one can imagine the following:

template <typename T1>
class Tuple {
   // singleton
   
};

template <typename T1, typename T2>
class Tuple {
   // pair
   
};

template <typename T1, typename T2, typename T3>
class Tuple {
   // three-element tuple
   
};

In the next section we discuss an application of such overloading.

The overloading isn’t necessarily restricted to the number of template parameters (such overloading could be emulated using partial specialization as is done for FunctionPtr in Chapter 22). The kind of parameters can be varied too:

template <typename T1, typename T2>
class Pair {
   // pair of fields
   
};

template <int I1, int I2>
class Pair {
   // pair of constant integer values
   
};

Although this idea has been discussed informally by some language designers, it has not yet been formally presented to the C++ standardization committee.

13.13 List Parameters

A need that shows up sometimes is the ability to pass a list of types as a single template argument. Usually, this list is meant for one of two purposes: declaring a function with a parameterized number of parameters or defining a type structure with a parameterized list of members.

For example, we may want to define a template that computes the maximum of an arbitrary list of values. A potential declaration syntax uses the ellipsis token to denote that the last template parameter is meant to match an arbitrary number of arguments:

#include <iostream>

template <typename T, ... list>
T const& max (T const&, T const&, list const&);

int main()
{
    std::cout << max(1, 2, 3, 4) << std::endl;
}

Various possibilities can be thought of to implement such a template. Here is one that doesn’t require new keywords but adds a rule to function template overloading to prefer a function template without a list parameter:

template <typename T> inline
T const& max (T const& a, T const& b)
{
    // our usual binary maximum:
    return a<b?b:a;
}

template <typename T, ... list> inline
T const& max (T const& a, T const& b, list const& x)
{
   return max (a, max(b,x));
}

Let’s go through the steps that would make this work for the call max(1, 2, 3, 4). Because there are four arguments, the binary max() function doesn’t match, but the second one does match with T = int and list = int, int. This causes us to call the binary function template max() with the first argument equal to 1 and the second argument equal to the evaluation of max(2, 3, 4). Again, the binary operation doesn’t match, and we call the list parameter version with T = int and list = int. This time the subexpression max(b,x) expands to max(3,4), and the recursion ends by selecting the binary template.

This works fairly well thanks to the ability of overloading function templates. There is more to it than our discussion, of course. For example, we’d have to specify precisely what list const& means in this context.

Sometimes, it may be desirable to refer to particular elements or subsets of the list. For example, we could use the subscript brackets for this purpose. The following example shows how we could construct a metaprogram to count the elements in a list using this technique:

template <typename T>
class ListProps {
  public:
    enum { length = 1 };
};

template <... list>
class ListProps {
  public:
    enum { length = 1+ListProps<list[1 ...]>::length };
};

This demonstrates that list parameters may also be useful for class templates and could be combined with the class overloading concept discussed earlier to enhance various template metaprogramming techniques.

Alternatively, the list parameter could be used to declare a list of fields:

template <... list>
class Collection {
    list;
};

A surprising number of fundamental utilities can be built on top of such a facility. For more ideas, we suggest reading Modern C++ Design (see [AlexandrescuDesign]), where the lack of this feature is replaced by extensive template- and macro-based metaprogramming.

13.14 Layout Control

A fairly common template programming challenge is to declare an array of bytes that will be sufficiently large (but not excessively so) to hold an object of an as yet unknown type T—in other words, a template parameter. One application of this is the so-called discriminated unions (also called variant types or tagged unions):

template <... list>
class D_Union {
  public:
    enum { n_bytes; };
    char bytes[n_bytes];  // will eventually hold one of various types
                          // described by the template arguments
    
};

The constant n_bytes cannot always be set to sizeof(T) because T may have more strict alignment requirements than the bytes buffer. Various heuristics exist to take this alignment into account, but they are often complicated or make somewhat arbitrary assumptions.

For such an application, what is really desired is the ability to express the alignment requirement of a type as a constant expression and, conversely, the ability to impose an alignment on a type, a field, or a variable. Many C and C++ compilers already support an __alignof__ operator, which returns the alignment of a given type or expression. This is almost identical to the sizeof operator except that the alignment is returned instead of the size of the given type. Many compilers also provide #pragma directives or similar devices to set the alignment of an entity. A possible approach may be to introduce an alignof keyword that can be used both in expressions (to obtain the alignment) and in declarations (to set the alignment).

template <typename T>
class Alignment {
  public:
    enum { max = alignof(T) };
};

template <... list>
class Alignment {
  public:
    enum { max = alignof(list[0]) > Alignment<list[1 ...]>::max
                  ? alignof(list[0])
                  : Alignment<list[1 ...]>::max; }
};

// a set of Size templates could similarly be designed
// to determine the largest size among a given list of types

template <... list>
class Variant {
  public:
    char buffer[Size<list>::max] alignof(Alignment<list>::max);
    
};

13.15 Initializer Deduction

It is often said that “programmers are lazy,” and sometimes this refers to our desire to keep programmatic notation compact. Consider, in that respect, the following declaration:

std::map<std::string, std::list<int> >* dict
= new std::map<std::string, std::list<int> >;

This is verbose, and in practice we would (and most likely should) introduce a typedef synonym for the type. However, there is something redundant in this declaration: We specify the type of dict, but it is also implicit in the type of its initializer. Wouldn’t it be considerably more elegant to be able to write an equivalent declaration with only one type specification? For example:

dcl dict = new std::map<std::string, std::list<int> >;

In this last declaration, the type of a variable is deduced from the type of the initializer. A keyword (dcl in the example, but var, let, and even auto have been proposed as alternatives) is needed to make the declaration distinguishable from an ordinary assignment.

So far, this isn’t a template-only issue. In fact, it appears such a construct was accepted by a very early version of the Cfront compiler (in 1982, before templates came on the scene). However, it is the verbosity of many template-based types that increases the demand for this feature.

One could also imagine partial deduction in which only the arguments of a template must be deduced:

std::list<> index = create_index();

Another variant of this is to deduce the template arguments from the constructor arguments. For example:

template <typename T>
class Complex {
  public:
    Complex(T const& re, T const& im);
    
};

Complex<> z(1.0, 3.0);  // deduces T = double

Precise specifications for this kind of deduction are made more complicated by the possibility of overloaded constructors, including constructor templates. Suppose, for example, that our Complex template contains a constructor template in addition to a normal copy constructor:

template <typename T>
class Complex {
  public:
    Complex(Complex<T> const&);

    template <typename T2> Complex(Complex<T2> const&);
    
};

Complex<double> j(0.0, 1.0);
Complex<> z = j;  // Which constructor was intended?

In the latter initialization, it is probable that the regular copy constructor was intended; hence z should have the same type as j. However, making it an implicit rule to ignore constructor templates may be overly bold.

13.16 Function Expressions

Chapter 22 illustrates that it is often convenient to pass small functions (or functors) as parameters to other functions. We also mention in Chapter 18 that expression template techniques can be used to build small functors concisely without the overhead of explicit declarations (see Section 18.3 on page 340).

For example, we may want to call a particular member function on each element of a standard vector to initialize it:

class BigValue {
  public:
    void init();
  
};

class Init {
  public:
    void operator() (BigValue& v) const {
        v.init();
    }
};
void compute (std::vector<BigValue>& vec)
{
    std::for_each (vec.begin(), vec.end(),
                   Init());
    
}

The need to define a separate class Init for this purpose is unwieldy. Instead, we can imagine that we may write (unnamed) function bodies as part of an expression:

class BigValue {
  public:
    void init();
  
};

void compute (std::vector<BigValue>& vec)
{
    std::for_each (vec.begin(), vec.end(),
                   $(BigValue&) { $1.init(); });
    
}

The idea here is that we can introduce a function expression with a special token $ followed by parameter types in parentheses and a brace-enclosed body. Within such a construct, we can refer to the parameters with the special notation $n, where n is a constant indicating the number of the parameter.

This form is closely related to so-called lambda expressions (or lambda functions) and closures in other programming languages. However, other solutions are possible. For example, a solution might use anonymous inner classes, as seen in Java:

class BigValue {
  public:
    void init();
  
};

void compute (std::vector<BigValue>& vec)
{
    std::for_each (vec.begin(), vec.end(),
                   class {
                     public:
                       void operator() (BigValue& v) const {
                           v.init();
                       }
                   };
                  );
  
}

Although these sorts of constructs regularly come up among language designers, concrete proposals are rare. This is probably a consequence of the fact that designing such an extension is a considerable task that amounts to much more than our examples may suggest. Among the issues to be tackled are the specification of the return type and the rules that determine what entities are available within the body of a function expression. For example, can local variables in the surrounding function be accessed? Function expressions could also conceivably be templates in which the types of the parameters would be deduced from the use of the function expression. Such an approach may make the previous example even more concise (by allowing us to omit the parameter list altogether), but it brings with it new challenges for the template argument deduction system.

It is not at all clear that C++ will ever include a concept like function expressions. However, the Lambda Library of Jaakko Järvi and Gary Powell (see [LambdaLib]) goes a long way toward providing the desired functionality, albeit at a considerable price in compiler resources.

13.17 Afternotes

It seems perhaps premature to talk about extending the language when C++ compilers are only barely becoming mostly compliant to the 1998 standard (C++98). However, it is in part because this compliance is being achieved that we (the C++ programmers community) are gaining insight into the true limitations of C++ (and templates in particular).

To meet the new needs of C++ programmers, the C++ standards committee (often referred to as ISO WG21/ANSI J16, or just WG21/J16) started examining a road to a new standard: C++0x. After a preliminary presentation at its April 2001 meeting in Copenhagen, WG21/J16 started examining concrete library extension proposals.

Indeed, the intention is to attempt as much as possible to confine extensions to the C++ standard library. However, it is well understood that some of these extensions may require work in the core language. We expect that many of these required modifications will relate to C++ templates, just as the introduction of STL in the C++ standard library stimulated template technology in the 1990s.

Finally, C++0x is also expected to address some “embarrassments” in C++98. It is hoped that doing so will improve the accessibility of C++. Some of the extensions in that direction were discussed in this chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.210.166