Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Overcoming the "Most Vexing Parse" Problem-2 : Page 2

The "most vexing parse" is a mysterious syntactic specter that the compiler interprets in the least expected manner, biting innocent programmers who think their code means something entirely different. Find out where that vexing parse might occur and how to avoid it.


advertisement

Unexpected Parsing Problem

Suppose you have two classes, T and U, where the constructor of U takes a single parameter, a const T&:

struct T { T(); }; struct U { U(const T& ); };



Your program instantiates an object of type U initialized by a temporary T object—or so you think:

U v(T()); //what does this really mean?

How would you interpret the preceding code? Intuitively, C++ programmers assume that the expression T() inside the parentheses creates a temporary T object passed by reference to the constructor of v. Indeed, some old compilers do interpret the code in this way. However, the entity T() in a declaration context can also mean something else—an abstract declarator (a declarator without an identifier). That is, the compiler interprets the sequence T()as a function with no parameters that returns T by value. When T() is interpreted as a function, not as a temporary object, the compiler silently converts T() to the T pointer to function of type: T(*)(). That is, a pointer to a function that returns T and takes no parameters.

Thus, the entire statement U v( T() ); gets interpreted as a declaration of a function named v that takes a pointer to function as a parameter, and returns U. This interpretation is very different from what the innocent programmer believes the code to mean.

Is the code ambiguous? Not really. A C++-conformant compiler must interpret the code as a function declaration, not as an object initialization, because C++ has a parsing precedence rule that says: "When a well-formed C++ statement can be interpreted as either a declaration or something else (an object definition for instance), the compiler should favor a declaration." This rule resolves the seemingly ambiguous U v( T() ); but leaves you with two new problems:

  • How can you initialize an object of type U with a temporary T object?
  • How do you declare a function named v that takes a pointer to a function and returns U without the risk of having programmers interpret the code the wrong way?

The best approach is defensive programming. That is, don't rely on brittle parsing rules and seemingly ambiguous code that compilers may interpret in different ways. Instead, write code that expresses your intent clearly. First, let's look at the initialization option.

Unambiguous Initialization

To initialize an object called v while avoiding the most vexing parse problem, add an extra pair of parentheses around the expression that creates a temporary T object:

U v( (T()) );

With the extra parentheses, the compiler can't treat the preceding line as a declaration; it must interpret it as a definition of an object v initialized by a temporary T. The ambiguity is gone, but the ugly syntax isn't. Fortunately, the new initialization syntax of C++0x allows you to rewrite the code, making your intent both explicit and more readable:

U v={T()}; //C++0x

No one would mistake this for a function declaration. Of course, the traditional = notation will also do the trick:

U v=T(); //v is an object initialized with a temp T

Personally, I prefer the new C++0x-style initialization notation, both because it's uniform and because it's possibly more efficient in some cases, as it enables the compiler to optimize away temporaries.

Unambiguous Function Declaration

If you want to declare a function, I do not recommend leaving the expression U v( T() ); as is. Although a standard conformant C++ compiler will unambiguously interpret that line as a function declaration, some compilers issue a warning when they encounter code that contains the most vexing parse. For example, the Apple-sponsored Clang compiler issues the following warning:

warning: parentheses were disambiguated as a function declarator
  U v( T() );
     ^~~~~~~

Sun's Studio 12 C++ 5.9 compiler requires the +w option to issue a warning in such cases; however, it issues a clearer message:

Warning: Declaration of "v" is a function, not an object.

Additionally, when writing portable code, you have to take into account that older compilers might still interpret the line in question as an object definition. It's best to stay away from the most vexing parse altogether.

To declare a function that takes a "pointer to function" parameter and returns U, use the following syntax instead:

U v( T(*)() ); // v is a function

As always, typedefs can simplify complex declarations:

typedef T(*funcptr)(); U v(funcptr); //v is a function

The most vexing parse might seem like more of a tricky job interview question than a real-world topic; however, it's more common than you might think, because many objects use other objects as initializers. When those initializers happen to be temporaries, you're likely to bump into the most vexing parse—not even knowing that the compiler distorted your intent. The best workaround is to write code that both human readers and compilers interpret unequivocally.



Danny Kalev is a certified system analyst and software engineer specializing in C++. He was a member of the C++ standards committee between 1997 and 2000 and has since been involved informally in the C++0x standardization process. He is the author of "The ANSI/ISO Professional C++ Programmer's Handbook" and "The Informit C++ Reference Guide: Techniques, Insight, and Practical Advice on C++."
Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap