| MSDN Home > MSDN Library > .NET Development > Visual Studio .NET > Articles and Columns > Columns > |
|
|
Bobby Schmidt July 9, 2002 Herein I offer a smorgasbord of template features required for conformance to the C++ standard yet missing from Microsoft® Visual C++® .NET. You can find a partial list of missing conformance features in the Visual C++ documentation. The list shows "some of the known places where the Visual C++ implementation does not agree with the C++ standard." The salient word is "some," as in "not all." Accordingly, I discuss some non-conformance problems that don't appear on the published list. Partial Specialization of Class TemplatesA class template has exactly one primary declaration: template<typename T>
class X
{
};
x<int> x1;
x<char> x2;
x<char *> x3;
This primary template is generic to a potentially infinite set of template arguments. The above primary template conceptually acts as if it were written template<typename T>
class X<T> // conceptual only; disallowed by C++ grammar
{
};
A template may also have any number of explicit specializations. Each specialization explicitly fixes all template parameters to a unique permutation of template arguments: template<typename T>
class X//<T>
{
};
template<
Between the poles of the general and the specific lies a middle path, what the C++ standard calls partial specializations. As that name implies, such specializations partially fix their template parameters: template<typename T>
class X
{
};
template<typename T>
class X<T *>
{
};
template<>
class X<char>
{
};
X<int> x1;
X<char> x2;
X<char *> x3; // uses partial specialization X<T *>
Visual C++ .NET self-destructs on the above example: 'T' : undeclared identifier
syntax error : '>'
syntax error : missing ';' before '{'
syntax error : missing ';' before '}'
cannot define a nested UDT of a template class out of line
unable to recover from previous error(s); stopping compilation
Like explicit specializations, partial specializations match restricted sets of arguments. (In this sense, explicit specializations are maximally restricted partial specializations.) And like a primary declaration, they match potentially infinite sets of arguments. Sometimes these infinite sets overlap, so that the same template arguments can match multiple partial specializations: template<typename T>
class X<T *>
{
};
template<typename T>
class X<T const *>
{
};
X<char const *> x4; // which is matched?
To detangle such ambiguity, the compiler ranks the candidate partial specializations into a so-called "partial ordering," using matching and ranking criteria defined by the C++ standard. According to that ordering, the specialization X<T const *> wins. Many modern C++ librariesincluding the C++ standard libraryrequire template partial specialization. Because our shipping compiler is lacking here, these libraries either don't work at all with Visual C++, or work with crippled semantics. You can expect that this is one of the first things we'll fix in subsequent releases of Visual C++. Partial Ordering of Function TemplatesThe standard also defines a partial ordering among overloaded function templates: template<typename T>
void f(T)
{
}
template<typename T>
void f(T *)
{
}
template<>
void f(char)
{
}
int main()
{
f(6); // uses f(T)
f("6"); // uses f(T *)
f('6'); // uses f(char)
}
Note The declaration of f(T *) is an overload, not a partial specialization. Regardless of contrary lore, there are no such things as partial specializations of function templates. Yes, there are explicit specializations of function templatesf(char) is an example. And no, I don't know why the terminology is inconsistent between class templates and function templates. Visual C++ mishandles partial ordering of function templates: int main()
{
f(6); // OK, correctly uses f<T>
f("6"); // error, can't decide which f to use
f('6'); // OK, correctly uses f<char>
}
This deficiency is unsurprising when you consider that the standard defines class-template ordering in terms of equivalent function-template ordering. Once Visual C++ supports the latter, the former will presumably fall out as a consequence. I leave this as an Exercise For The Student: template<typename T>
void f(T const *)
{
}
template<>
void f(char *)
{
}
int main()
{
f("Which overload should this match?");
}
Class Template Parameter NamesA class template's primary definition typically mentions the template's parameter(s) by name: template<typename T> // T is the template parameter
class X
{
void f(T); // T mentioned here...
};
But consider the explicit specialization: template<> // no template parameter
class X<int>
{
void f(T); // Does this still work?
};
Is the parameter name T still in scope here and available for name resolution, even though the name is not literally declared in this specialization? According to my reading of the C++ standard, the answer is "no." According to Visual C++ .NET, the answer is yes: The above compiles and runs as if the specialization were declared: template<>
class X<int>
{
typedef int T;
void f(T);
};
Member-Template DefinitionsSome classes and class templates may contain members that are themselves templates. As with other kinds of members, the C++ standard allows a member-template definition to appear either as its declaration: class X
{
template<typename T>
T f(T)
{
return T();
}
};
or separate from its declaration: class X
{
template<typename T>
T f(T);
};
template<typename T>
T X::f(T)
{
return T();
}
According to the Visual C++ .NET documentation, the second example shouldn't compile. The docs are in errormy example does compile. However, if X is itself a template rather than an ordinary class: template<typename T2>
class X
{
template<typename T>
T f(T);
};
template<typename T2>
template<typename T>
T X<T2>::f(T)
{
return T();
}
or if the member is an operator: class X
{
template<typename T>
operator T();
};
template<typename T>
X::operator T()
{
return T();
}
then Visual C++ .NET fails as promised. The workaround: Define the offending members at their points of declaration, just as you must for members in C# or Java. class X
{
template<typename T>
operator T()
{
return T();
}
};
Explicit Specialization of Member TemplatesLike other templates, member templates may have explicit specializations. Continuing the recent example from above: class X
{
template<typename T>
T f(T)
{
return T();
}
};
template<>
int X::f(int)
{
return int();
}
For this code Visual C++ generates the error: 'int X::f(int)' : overloaded member function not found in 'X' Trying to make the error go away, you might be tempted to declare the specialization in X: class X
{
template<typename T>
T f(T)
{
return T();
}
template<>
int f(int);
};
This still breaks, and rightly so: According to the standard, the declaration must appear in the namespace scope containing X, which in this case is global scope. I don't know a workaround for this. For now you apparently can't specialize template members. Nested Classes Within Class TemplatesAccording to the standard, class templates may contain declarations of nested classes: template<typename T>
class X
{
class Y;
};
Visual C++ accepts this, as well as the nested definition: template<typename T>
class X
{
class Y
{
}
};
But if you try moving the Y definition outside X: template<typename T>
class X
{
class Y;
};
template<typename T>
class X<T>::Y // error here, but should be okay
{
};
the compiler incorrectly greets you with: 'T' : undeclared identifier cannot define a nested UDT of a template class out of line unable to recover from previous error(s); stopping compilation These are the same messages generated by my partial-specialization example. When you combine this with the specialization problem in the previous item, you're left with a dilemma: To work around conformance problems with templates, you sometimes must define members with their declarations, and you sometimes must define members separate from their declarations. There is no easy one-size-fits-all rule you can apply to bypass all of these limitations. Even if Visual C++ .NET were completely conformant here, you wouldn't necessarily apply the same style rule everywhere. When I first learned C++, I almost always separated member definitions from their declarations: class X
{
public:
X(int = 10, char = '\0');
X &operator=(X const &);
long const *f() const;
// ...
};
X::X(int i, char c)
{
// ...
}
X &X::operator=(X const &that)
{
// ...
}
long const *X::f() const
{
// ...
}
As a C programmer I found this more naturally C-like: The class definition was compact and could serve as a quick reference, while the function definitions were spread out and easier to read. I also found it quicker to port C code into this style of C++ code. Padding the code with extra blank lines and repeated function declarations seemed a reasonable price for the gain in clarity and porting. I reconsidered my style in the wake of templates, which extract a higher verbiage cost for the same gain: template<class T>
class X
{
public:
X(int = 10, char = '\0');
X &operator=(X const &);
long const *f() const;
// ...
};
template<class T>
X<T>::X(int i, char c)
{
// ...
}
template<class T>
X<T> &X<T>::operator=(X const &that)
{
// ...
}
template<class T>
long const *X<T>::f() const
{
// ...
}
I finally yielded when confronted with STL and STLesque templates, deciding the likes of template<typename T = int, typename P = T *>
class X
{
public:
template <unsigned N = 10>
class X2
{
public:
template<typename T2>
static P f(T2 const &)
{
return new T[x][N];
}
};
};
hurt enough without compounding the fracture: template<typename T = int, typename P = T *>
class X
{
public:
template <unsigned N = 10>
class X2;
};
template<typename T, typename P>
template<unsigned N>
class X<T, P>::X2
{
public:
template<typename T2>
static P f(T2 const &);
};
template<typename T, typename P>
template<unsigned N>
template<typename T2>
P X<T, P>::X2<N>::f(T2 const &x)
{
return new T[x][N];
}
Dependent-Name ResolutionWithin a template, some types and expressions have meanings dependent on the template parameters: template<typename T, unsigned N>
void f()
{
T a[N];
sizeof a;
}
int main()
{
f<int, 7>();
}
Within the body of f, the type T and the constant expression N trivially depend on their eponymous template parameters. In addition:
The compiler can't reckon the meaning of T and N solely in the context of f's definition, but instead must fix T and N at the point of f's instantiation. This implies that the type and size of a also can't be known until instantiation. In my example, the call f<int, 7>(); instantiates template<>
void f<int, 7>()
{
int a[7];
sizeof(a);
}
fixes T and N, and therefore fixes the type and size of a. Note that the same two-phase mechanism can produce bifurcated error messages. If you change the call to f<int, 0>(); the compiler can bind template argument 0 to template parameter unsigned N. In the definition of f<int, 0>, however, the compiler can't form int a[0]; since arrays can't be defined with zero size. Visual C++ .NET correctly fails to compile the call, and generates error messages for both the site of the definition and the site of the instantiation. A More Elaborate ExampleHere's one that Visual C++ .NET eventually gets wrong: template<typename T>
struct X
{
X(T *p = 0)
{
f(p);
}
};
class Z;
X<Z> x;
What does f mean? At first glance you might think that f must name something visible in the definition of X, and is thus a non-dependent name. If I declare such a "something": void f(void *); template<typename T> class X // ... the x definition effectively instantiates template<>
struct X<Z>
{
X(Z *p = 0)
{
::f(p);
}
};
Next move Z into a namespace: // ...
namespace N
{
class Z;
}
X<N::Z> x;
x still instantiates as template<>
struct X<N::Z>
{
X(N::Z *t = 0)
{
::f(t);
}
};
Finally, add one more member to the namespace: // ...
namespace N
{
class Z;
void f(Z *);
}
X<N::Z> x;
Visual C++ .NET still (and incorrectly) resolves f as ::f. But what it should do is resolve f as N::f, so that the instantiation becomes: template<>
struct X<N::Z>
{
X(N::Z *t = 0)
{
N::f(t);
}
};
A conforming compiler magically interprets unqualified f as N::f even though:
The magic involves what the standard calls "argument-dependent name lookup," or what the rest of the world calls "Koenig lookup." Such lookup lets a function's name be found in scopes dependent on the function's arguments. Koenig lookup greatly increases the complexity of name resolution; at the same time, it allows expressions such as cout << x that would otherwise be impossible for arbitrary x. Visual C++ .NET has problems with Koenig lookup outside of templatesa topic I'm saving for a later column. export and Exported TemplatesThe horror. The horror. export allows "separation model" templates, which differ from the "inclusion model" templates you've comes to know and, er, know:
Here's the requisite dysfunctional example: export template<typename T> class X; Visual C++ .NET can't handle this. Verily, at the time of my writing (late June), no released and shipping commercial compiler can handle this. Of all the language features required by the C++ standard, export and exported templates are the last unimplemented frontier. That frontier is about to be homesteaded: The wizards at Edison Design Group have released version 3.0 of their C++ front end, and that translator can compile my example. As I've mentioned before, EDG doesn't implement full-blown compilers, but instead licenses its front end to others who incorporate the technology. Of the vendors using EDG's translator, Greg Comeau seems closest to shipping a commercial export-aware compiler. That export has been in the standard for five years, yet only now is being implemented, betrays the feature's complexity, cost, and relative merit. export started life as a theoretical invention of the C++ committee rather than codification of demonstrated, understood, and accepted practice. Every other significant language feature invented by the committeeexceptions, namespaces, RTTI, templateshas spawned unforeseen and unintended consequence for both language users and language implementers. I have every reason to believe that export will follow this unfortunate legacy with vigor: It doesn't fix the problems it's allegedly designed to fix, actually makes some of those problems worse, threatens the semantics of translation units and name resolution, impedes future language innovation, seems impossible to fully specify, and is deceptively expensive to implement. What a bargain! Of all the non-conformance issues facing Visual C++ .NET, the lack of export should concern you the least. Even if the compiler supported export, you very likely wouldn't want to use it. If you're not convinced, or want to understand why many of us think export is a failed experiment, I encourage you to read Herb Sutter's upcoming columns in the September and November issues of C/C++ Users Journal. CodaThis column covers significant template-related problems, issues involving Clause 14 of the C++ standard. My next column or two will dissect conformance problems not specifically related to templates. ErraticaIn an earlier column discussing conformance features that newly work in Visual C++ .NET, I show an example of non-type template parameters: template<int N> class X; X<10> x; and imply that the example should work (but doesn't) in Visual C++ 6.0. Oops. As I originally wrote it, the example is non-conformant: The type X<int> is incomplete at the point of x's declaration. That it fails in Visual C++ 6.0 is good. What I meant to write was: template<int N>
class X
{
};
X<10> x;
This corrected example actually works in Visual C++ 6.0, and that's also good. Well, it's good for the compiler, but not so good for the column, since it defeats the example's purpose. Accordingly, I'll be removing the example from that column.
Deep C++ .NET |
| Contact Us | E-Mail this Page | MSDN Flash Newsletter |
| © 2002 Microsoft Corporation. All rights reserved. Terms of Use Privacy Statement Accessibility |