Most vexing parse
The most vexing parse is a counterintuitive form of syntactic ambiguity resolution in the C++ programming language. In certain situations, the C++ grammar cannot distinguish between the creation of an object parameter and specification of a function's type. In those situations, the compiler is required to interpret the line as a function type specification.
Occurrence
The term "most vexing parse" was first used by Scott Meyers in his 2001 book Effective STL.[1] While unusual in C, the phenomenon was quite common in C++ until the introduction of uniform initialization in C++11.[2]
Examples
C-style casts
A simple example appears when a functional cast is intended to convert an expression for initializing a variable:
void f(double my_dbl) { int i(int(my_dbl)); }
Line 2 above is ambiguous. One possible interpretation is to declare a variable i
with initial value produced by converting my_dbl
to an int
. However, C allows superfluous parentheses around function parameter declarations; in this case, the declaration of i
is instead a function declaration equivalent to the following:
// A function named i takes an integer and returns an integer. int i(int my_dbl);
Unnamed temporary
A more elaborate example is:
struct Timer {}; struct TimeKeeper { explicit TimeKeeper(Timer t); int get_time(); }; int main() { TimeKeeper time_keeper(Timer()); return time_keeper.get_time(); }
The line
TimeKeeper time_keeper(Timer());
is ambiguous, since it could be interpreted either as
- a variable definition for variable
time_keeper
of classTimeKeeper
, initialized with an anonymous instance of classTimer
or - a function declaration for a function
time_keeper
that returns an object of typeTimeKeeper
and has a single (unnamed) parameter, whose type is a (pointer to a) function[Note 1] taking no input and returningTimer
objects.
The C++ standard requires the second interpretation, which is inconsistent with line 9 above. For example, Clang++ warns that the most vexing parse has been applied on line 9 and errors on the following line:[3]
$ clang++ time_keeper.cc timekeeper.cc:9:25: warning: parentheses were disambiguated as a function declaration [-Wvexing-parse] TimeKeeper time_keeper(Timer()); ^~~~~~~~~ timekeeper.cc:9:26: note: add a pair of parentheses to declare a variable TimeKeeper time_keeper(Timer()); ^ ( ) timekeeper.cc:10:21: error: member reference base type 'TimeKeeper (Timer (*)())' is not a structure or union return time_keeper.get_time(); ~~~~~~~~~~~^~~~~~~~~
Solutions
The required interpretation of these ambiguous declarations is rarely the intended one.[4][5] Function types in C++ are usually hidden behind typedefs and typically have an explicit reference or pointer qualifier. To force the alternate interpretation, the typical technique is a different object creation or conversion syntax.
In the type conversion example, there are two alternate syntaxes available for casts: the "C-style cast"
// declares a variable of type int int i((int)my_dbl);
or a named cast:
int i(static_cast<int>(my_dbl));
In the variable declaration example, the preferred method (since C++11) is uniform (brace) initialization.[6] This also allows limited omission of the type name entirely:
//Any of the following work:TimeKeeper time_keeper(Timer{}); TimeKeeper time_keeper{Timer()}; TimeKeeper time_keeper{Timer{}}; TimeKeeper time_keeper( {});
TimeKeeper time_keeper{ {}};
Prior to C++11, the common techniques to force the intended interpretation were use of an extra parenthesis or copy-initialization:[5]
TimeKeeper time_keeper( /*Avoid MVP*/ (Timer()) ); TimeKeeper time_keeper = TimeKeeper(Timer());
In the latter syntax, the copy-initialization is likely to be optimized out by the compiler.[7] Since C++17, this optimization is guaranteed.[8]
Notes
- ↑ According to C++ type decay rules, a function object declared as a parameter is equivalent to a pointer to a function of that type. See Function object.
References
- ↑ Meyers, Scott (2001). Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library. Addison-Wesley. ISBN 0-201-74962-9.
- ↑ Coffin, Jerry (29 December 2012). "c++ - What is the purpose of the Most Vexing Parse?". https://stackoverflow.com/questions/14077608/what-is-the-purpose-of-the-most-vexing-parse.
- ↑ Lattner, Chris (5 April 2010). "Amazing Feats of Clang Error Recovery". The Most Vexing Parse. https://blog.llvm.org/posts/2010-04-05-amazing-feats-of-clang-error-recovery/.
- ↑ DrPizza; Prototyped; wb; euzeka; Simpson, Homer J (October 2002). "C++'s "most vexing parse"". https://arstechnica.com/civis/viewtopic.php?f=20&t=767929.
- ↑ 5.0 5.1 Boccara, Jonathan (2018-01-30). "The Most Vexing Parse: How to Spot It and Fix It Quickly" (in en-US). https://www.fluentcpp.com/2018/01/30/most-vexing-parse/.
- ↑ Stroustrup, Bjarne (19 August 2016). "C++11 FAQ" (in en). Uniform initialization syntax and semantics. https://www.stroustrup.com/C++11FAQ.html#uniform-init.
- ↑ "Myths and urban legends about C++". What is copy elision? What is RVO?. https://isocpp.org/wiki/faq/myths#copy-elision.
- ↑ Devlieghere, Jonas (2016-11-21). "Guaranteed Copy Elision" (in en). https://jonasdevlieghere.com/guaranteed-copy-elision/. Note, however, the caveats covered in Brand, C++ (2018-12-11). "Guaranteed Copy Elision Does Not Elide Copies" (in en-US). https://devblogs.microsoft.com/cppblog/guaranteed-copy-elision-does-not-elide-copies/.
External links
- Discussion in the C++03 standard final draft (see §8.2 Ambiguity resolution [dcl.ambig.res]): https://web.archive.org/web/20141113085328/https://cs.nyu.edu/courses/fall11/CSCI-GA.2110-003/documents/c++2003std.pdf
- CppReference on direct initialization (the sort vulnerable to the most vexing parse): https://en.cppreference.com/w/cpp/language/direct_initialization
Original source: https://en.wikipedia.org/wiki/Most vexing parse.
Read more |