Geoff Chappell - Software Analyst
expected '(' to follow 'identifier'
The only known case of this error is that the token immediately after a __declspec keyword is not a left parenthesis. For who knows what reason, the product documentation for error C2054 does not mention this case.
The C++ compiler does have code for another case, but it appears (as a tentative finding) to be unreachable because the necessary conditions are all caught elsewhere. This is why, for instance, the product documentation’s example for error C2054 actually produces C2470 from the C++ compiler. Is Microsoft truly unable to employ anyone to check such things?
The message text in the __declspec case goes wrong on two counts. First, the relevant code presents the wrong token for resolving the identifier placeholder. The token that fits the text is the __declspec, which is what the '(' was expected to follow. What is presented instead is the token that followed the __declspec. The '(' was not expected to follow this token, but was expected instead of this token.
Were this the only problem, compiling
__declspec rubbish (); // C2054
would produce the odd complaint about having missed an expectation that actually is satisfied:
TEST.CPP(1) : error C2054: expected '(' to follow 'rubbish'
However, this nonsense is escaped because of a second coding error. Whether the chosen token is right or wrong, the description presented for output as the identifier placeholder does not match correctly the format specifier that stands for this placeholder in the message text as read from the resource strings. The usual result is that the information given is not meaningful for the format specifier and gets represented by a default, as shown by the actual error message for the preceding source fragment:
TEST.CPP(1) : error C2054: expected '(' to follow '<Unknown>'
Of course, it can happen (or be contrived) that the token after the __declspec is one whose description is meaningful for the placeholder resolution. The misbehaviour can then be calculated for greater effect.
The compiler’s resource strings have their own scheme of format specifiers, similar to and extending the scheme of the printf function from the C Run-Time Library. The distinctive specifiers in the compiler’s scheme begin %$. For instance, code to resolve %$I is to provide as the matching argument a pointer to an instance of the structure with which the compiler represents an identifier. There are many more: %$S for a symbol, %$T for a type, etc. There is also an indirect specifier %$* in which the * means that code should provide first a character to take the place of the * and then whatever argument would be expected had the * been replaced all along.
The resource string for error C2054 has %$* for resolution of the placeholder that the product documentation labels identifier. However, the code that prepares the C2054 error message in the __declspec case presents as its one and only argument a 16-bit value that describes the type of token that follows the __declspec. (A correction would be to set as the first argument the character 'L' and follow with this 16-bit token-type value.) We can now set about contriving code fragments in which this token-type value is meaningful as one of the characters that complete a %$ format specifier.
For more predictability however, we should want to know what will be seen as the expected second argument. This depends on the precise sequence of CPU instructions in the relevant code and is likely to vary significantly between builds of the compiler. For version 13.00.9466, what will be seen as the second argument is whatever happens to have been in register EDI and it turns out that this is reliably the address of a predictable structure. Indeed, it is at least in part a controllable structure, since the dword at offset 0x08 is a counter. It happens that offset 0x08 is also significant in the structure the compiler uses for representing a type. Specifically, it is the site of a value that summarises the type, e.g., 0x10 for bool, 0x11 for char, 0x40 for void. One of the format specifiers that can show a type is %$B and 'B' works out as the token-type value for :: (i.e., the scope-resolution operator). By contriving a :: immediately after a __declspec, we expect to induce the compiler to dump a type but with the type described erroneously by the counter.
Especially if the following fragment were expanded by giving the function a body,
void __declspec :: operator delete (void *); // C2054
the __declspec would be at least vaguely plausible as an incomplete removal of what once was valid and is now unwanted, but the programmer who didn’t notice his slip at editing is hardly likely to spot it from the error message
TEST.CPP(1) : error C2054: expected '(' to follow ''
(By the way, operator delete is not essential to the example. Any function will do, but it must have a prior declaration at file scope. The fragment as given works as a one-line source file because the global operator delete is declared among the predefined C++ types that Microsoft includes for free with every C++ source file.)
The reason the error message for the example as given says essentially that a '(' was expected to follow nothing is that the counter (used erroneously) is only 1, due to the void before the __declspec, but 1 is not valid for describing a type. The least value that is valid is 16, for bool. To get the counter to 16, insert 15 storage specifiers before the faulty __declspec. It doesn’t matter what the specifiers are, but the easiest are __declspec keywords that have empty argument lists, as in
void __declspec () ... __declspec () __declspec :: operator delete (void *); // C2054
This indeed produces
TEST.CPP(17) : error C2054: expected '(' to follow 'bool'
even if compiled with the /noBool option.
Among other tokens that induce misbehaviour immediately after __declspec are namespace and throw, which resolve respectively to %$d (to dump a decimal) and %$P (to dump a position, i.e., filename and line number). For example, without trying to dress the code for syntactic validity beyond the problem case, compile
__declspec throw // C2054 and more
and get something like
TEST.CPP(1) : error C2054: expected '(' to follow 'TEST.CPP(1241268)'
as the first error (with several more as the compiler attempts to continue past the error).