Click here to Skip to main content
15,881,803 members
Articles / Programming Languages / C++
Article

Standard Features Missing From VC++ 7.1. Part III: Two-Phase Name Lookup

Rate me:
Please Sign up or sign in to vote.
4.81/5 (11 votes)
6 Oct 20047 min read 73.9K   16   13
Issues with two-phase name lookup explained

Introduction

In the third and final installment of my Standard Features Missing From VC++ 7.1 series, I want to describe two–phase name lookup. If you read my previous two articles, you noticed that I started with the easiest to understand and most widely implemented of the three missing features – exception specifications; then I described keyword export which is implemented by only one compiler at this point in time, but still pretty easy to grasp for anybody who has ever worked with templates. Here, I am going to talk about a not so well known standard feature that usually irritates people by breaking their code when they switch to a compiler that supports it. For instance, many GCC developers were unpleasantly surprised after upgrading to the version 3.4 of their compiler. Although at this point there is no sign from Microsoft that they plan to implement two-phase name lookup, I still think it is good to know how to write the code that would not break on a compiler that supports this feature – especially since it is very easy to do.

I guess it would be fair to stress that this article contains material that many C++ developers do not need to know about, and do not want to know about. It is one of the many language subtleties that makes C++ so interesting, complicated, liked, hated, fun, boring – pick whatever you like. If you feel this topic is not practical enough for you to spend your time reading this article, no hard feelings from my side.

Background

The first time I remember hearing about two-phase name lookup was after I posted my article  RAII Idiom and Managed C++[^]. In this article, I described a policy-based template wrapper that brings the power of deterministic cleanup to the world of managed code. The wrapper looks like this:

C++
template <typename T, template <typename> 
                class CleanupPolicy = DisposeObject>
class gc_scoped : protected CleanupPolicy<T> {
   gcroot <T> object_;

   // Non - copyable
   gc_scoped ( const gc_scoped& );
   gc_scoped& operator = ( const gc_scoped& );

   public:
   gc_scoped (const T& object): object_(object){}
   ~gc_scoped (){
        Cleanup(object_);
    }
    T operator-> () const { return object_; }
 };

I was very pleased with the responses this article got from the readers. However, someone with a strange nickname hgrund (obviously a very knowledgeable person who has unfortunately left only 3 messages at Code Project) warned me that a standard-compliant C++ compiler would reject this code. Such compiler would not find the function Cleanup() (called from the destructor of my wrapper) because it would not look into the base class CleanupPolicy<T> for it. To make the above code standard compliant, hgrund suggested to either use the qualified name when invoking Cleanup(), or to explicitly use pointer this:

CleanupPolicy<T>::Cleanup(object_);

or

this->Cleanup(object_);

Of course, I was too lazy to fix the article, but at least I took time to read a bit more about looking up names in template definitions and understand the issue of two phase name lookup. Since I couldn’t find a good explanation of this feature on the internet (Herb Sutter started a GoW article on it, but has not finished it at the time of this writing), I hope you won’t mind if I share my findings with you. For the in-depth knowledge on the subject, please look at the literature listed at the end of the article.

Dependent and Nondependent Names

Before jumping to the two-phase name lookup, it is important to understand the difference between dependent and nondependent names in template definitions.

A dependent name is a name that in some way depends on a template parameter [1]. The Standard [2] distinguishes between type-dependant (depend on the type of a template parameter) and value-dependant (depend on the value of a non-type template parameter) names. While the Standard thoroughly lists conditions for a name to be dependent, I will outline only some interesting cases of dependent names here [1][2]:

  • A template parameter itself (obviously).
  • A name that contains a template parameter.
  • An array type constructed from a dependent type, or having constant size specified by a value-dependent constant expression.
  • this, if the enclosing class type is dependent.
  • An expression, if any of its arguments has a dependent type.
  • A variable is dependent if its type is dependent.

For instance, in the following example:

template<class T, int Size>
class Example : public Base<T> {
int myArray_[Size];
std::vector<int> myVector_;
typename T::iterator iterator;
void func (T* arg) {
  using namespace std;
  int size = arg->GetSize();
  for (int i = 0; i < size; ++i)
    cout << i;
  }
};

Names T, Size, Example, Base<T>, myArray_, iterator, func, arg and GetSize are dependent. On the other hand, names std::vector, size, i and cout are nondependent. Although the rules listed in the Standard look scary, in practice it is usually not very hard to conclude which names are dependent.

The Two-Phase Name Lookup

Now that we know the difference between dependent and nondependent names, let’s go straight to the two-phase name lookup rule. The names in template definitions are looked up in two phases:

  1. Parsing of a template. This phase occurs when a template definition is first seen by a compiler (point of definition [3]). During this phase, the lookup is completed only for nondependent names.
  2. Template instantiation. It happens when a template is instantiated, with template parameters substituted by the actual template arguments (point of instantiation). It is during this phase when the dependent names are looked up.

What is the reason for this splitting name lookup into phases? For one thing, we want names to be looked up early, to spot errors when a template is parsed, rather than used. However, dependent names simply cannot be looked up before the template arguments are known. Therefore, only nondependent names are bound at the point of definition of a template, and dependent names are bound at the point of instantiation, when a template is actually used.

Of course, compilers that do not support two-phase name lookup (vast majority do not at the time of this writing) will look up for both dependent and non-dependent names in the point of instantiation. With them, template instantiation looks a bit like expanding macros (and proponents of .NET generics never fail to mention this when comparing them to templates).

Problems with the Two-Phase Name Lookup

As I already pointed out, when a developer switches to a compiler that supports this feature (EDG/Comeau, Metrowerks and GCC 3.4 at this point) template code that worked quite well may fail to compile. Take this simple (albeit useless) piece of code:

template <class T> void f(){ C c; }
class C {};
int main(){
f<C>();
}

When compiled with MSVC++ 7.1, I get the following result:

Compiling...
main.cpp
c:\articles\TwoPhaseLookup\main.cpp(3) : warning C4101: 'c' : unreferenced 
local variable  
c:\articles\TwoPhaseLookup\main.cpp(9) : see reference to function template
 instantiation 'void f<C>(void)' being compiled
Linking...

Build log was saved at "file://c:\articles\TwoPhaseLookup\Debug\BuildLog.htm"
TwoPhaseLookup - 0 error(s), 1 warning(s)

However, if I try to compile it with Comeau 4.3.3 (online version), errors are triggered:

Comeau C/C++ 4.3.3 (Aug  6 2003 15:13:37) for ONLINE_EVALUATION_BETA1
Copyright 1988-2003 Comeau Computing.  All rights reserved.
MODE:strict errors C++

"ComeauTest.c", line 2: error: identifier "C" is undefined
  void f(){ C c; }
            ^

"ComeauTest.c", line 2: warning: variable "c" was declared but never 
referenced
  void f(){ C c; }
              ^

"ComeauTest.c", line 2: error: expected a ";" (perhaps on the previous 
statement)
  void f(){ C c; }
              ^
          detected during instantiation of "void f<T>() [with T=C]" 

2 errors detected in the compilation of "ComeauTest.c".

What happened here? A standard conforming compiler (Comeau) looked up for the nondependent name C when first encountering the template definition. Since C was not yet defined at this point, the compiler reported errors. On the other hand, MSVC++ 7.1 which does not implement two-phase lookup looked up for C at the point of instantiation (within function main() ) and by that time it was already defined.

To make this sample compile on a standard-conformant compiler, we would need to define C before f:

class C {};
template <class T> void f(){ C c; }
int main(){
f<C>();
}

The code listed above will compile fine on both Comeau 4.3.3 and MSVC++ 7.1; only a warning for a declared but never used variable will be issued.

Another example is my above-mentioned gc_scoped wrapper:

template <typename T, template <typename> 
                class CleanupPolicy = DisposeObject>
    class gc_scoped : protected CleanupPolicy<T> {
...
~gc_scoped (){
         Cleanup(object_);
     }

This works as-is (assuming our CleanupPolicy argument contains Cleanup()), because MSVC++ 7.1 does not look for nondependent names when parsing a template. If it did, it would have not considered CleanupPolicy when looking up for Cleanup() in the first phase of name lookup (how it could? The actual template argument is not yet known at this stage) and therefore it would have reported a compile error. To make it work, we need to make compiler aware that Cleanup() is really dependent on a template parameter, and as already pointed out, we can do it either by fully qualifying it, or explicitly using pointer this. At one point I was surprised this was necessary, because Cleanup() was invoked with a dependent argument, and therefore I thought it should be dependant as well. However, it seems that compiler needs too see a function definition to consider it dependent. Invocation is not enough.

Conclusion

Although not many compilers implement two-phase name lookup, it is generally considered a useful feature, and it would be good to have it in MSVC++. In the meantime, we should be aware of it, and write template code that will not break when compiled with a standard conformant compiler.

With this article, I wrap up this mini series on standard conformance issues with Microsoft Visual C++ 7.1. I aimed to help fellow developers avoid writing non-standard code that will break when ported to a standard compliant compiler. Also, since Microsoft generally listens to customers’ feedback, I hope VC++ developers will influence them to put standard compliance higher on their priority list.

References

  1. David Vandevoorde, Nicolai M. Josuttis: "C++ Templates: The Complete Guide", Addison Wesley Professional; ISBN: 0201734842 ; 1st edition (November 12, 2002)
  2. ISO/IEC 14882 Programming languages - C++, 1998
  3. Bjarne Stroustrup, "The C++ Programming Language", 3rd edition, Addison-Wesley, 1997

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior)
United States United States
Born in Kragujevac, Serbia. Now lives in Boston area with his wife and daughters.

Wrote his first program at the age of 13 on a Sinclair Spectrum, became a professional software developer after he graduated.

Very passionate about programming and software development in general.

Comments and Discussions

 
GeneralAnother point about Two-Phase Lookup Pin
Dave Handley15-Oct-04 12:01
Dave Handley15-Oct-04 12:01 
GeneralRe: Another point about Two-Phase Lookup Pin
Nemanja Trifunovic16-Oct-04 3:47
Nemanja Trifunovic16-Oct-04 3:47 
GeneralAbout Cleanup() Pin
MoonlightM14-Oct-04 7:49
MoonlightM14-Oct-04 7:49 
GeneralRe: About Cleanup() Pin
Nemanja Trifunovic14-Oct-04 9:12
Nemanja Trifunovic14-Oct-04 9:12 
GeneralThanks for the heads up! Pin
Jörgen Sigvardsson6-Oct-04 12:02
Jörgen Sigvardsson6-Oct-04 12:02 
GeneralRe: Thanks for the heads up! Pin
Nemanja Trifunovic14-Oct-04 9:10
Nemanja Trifunovic14-Oct-04 9:10 
GeneralRe: Thanks for the heads up! Pin
TTimo17-Oct-05 16:02
TTimo17-Oct-05 16:02 
GeneralCongrats and a suggestion Pin
Joaquín M López Muñoz6-Oct-04 8:57
Joaquín M López Muñoz6-Oct-04 8:57 
GeneralRe: Congrats and a suggestion Pin
Jörgen Sigvardsson6-Oct-04 12:01
Jörgen Sigvardsson6-Oct-04 12:01 
GeneralRe: Congrats and a suggestion Pin
Joaquín M López Muñoz6-Oct-04 12:22
Joaquín M López Muñoz6-Oct-04 12:22 
GeneralRe: Congrats and a suggestion Pin
Jörgen Sigvardsson6-Oct-04 12:31
Jörgen Sigvardsson6-Oct-04 12:31 
GeneralRe: Congrats and a suggestion Pin
Joaquín M López Muñoz6-Oct-04 12:52
Joaquín M López Muñoz6-Oct-04 12:52 
GeneralRe: Congrats and a suggestion Pin
Nemanja Trifunovic14-Oct-04 9:07
Nemanja Trifunovic14-Oct-04 9:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.