Flylib.com

Books Software

 
 
 

13.4 String Literal and Floating-Point Template Arguments

Ru-Brd

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).

Ru-Brd
Ru-Brd

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.

Ru-Brd