Visual C++ Compiler Warning C4038

Message Text

'modifier' : illegal class modifier

The product documentation has “illegal ambient class modifier”. The inserted “ambient” is correct with respect to the circumstances of the warning, but incorrect as a representation of the text as actually produced by the compiler.

As discussed below, the text anyway misleads by speaking just of one modifier. The singular is correct only in the sense that multiple modifiers count collectively as one (compound) modifier. Moreover, if the message does cite more than one modifier, not all need be illegal.

While on the matter of precision in the message text, note now that “class” stands not just for class, struct, union or __interface, as usual, but also for enum.

Severity

This is a level 1 warning.

Circumstances

Warning C4038 is concerned with storage modifiers in the declarations of user-defined types (including enumerations), specifically where placed between the class-key or enum and the name of the type. Such modifiers are said to be ambient because they affect the type, and thus all instances of the type. By contrast, modifiers placed before the class-key or enum affect just the one instance of the type, and are discarded, with a warning (C4091), if it turns out that no instance is declared.

Thus (with enumerations counting as class types just for present purposes),

class-key [ambient-modifiers] class-name;

declares a type, optionally sets some storage attributes for the type and names the type, but does not completely define the type: no members are specified and instances of the type cannot yet be constructed. A member list can be given separately, and the ambient modifiers need not be repeated:

class-key [ambient-modifiers] class-name
{
    member-list
};

Indeed, ambient modifiers can be added (subject to conflicts with ones already specified). They accumulate even while declaring an instance of the type:

instance-modifiers [class-key [ambient-modifiers]] class-name instance-name;

At least, that’s the theory without complication by various keywords specific to Microsoft’s managed extensions, namely __abstract, __gc, __nogc, __sealed and __value, and by __event. All these must for some reason be placed before the class-key if they are to be recognised, yet all then have an ambient effect, seemingly in contradiction to what seems to have been the general principle (and would seem to be still the general principle as far as concerns the product documentation’s Grammar of Classes).

Prohibition

The following are not permitted as ambient modifiers:

(plus one other that has not yet been identified). In practice however, only the __declspec modifiers produce warning C4038 since the others trigger other warnings or errors before the validity of ambient modifiers gets tested.

If among the ambient modifiers any one is illegal, the message text will show as the modifier a selection of all the ambient modifiers, whether legal or not, according to the standard representation of storage specifiers.

The product documentation claims that a modifier becomes illegal (for warning C4038) because of its use “for classes with dllimport or dllexport attributes.” Whether this was ever correct, it is certainly rubbish now. Neither of those attributes is needed for the modifiers listed above to be illegal as ambient  modifiers.

Examples

For a simple example of no practical relevance (and no meaningfulness even in theory), simply compile

enum __declspec (nothrow) Test;

For a more elaborate and perhaps even plausible example, begin with the valid code

#pragma section ("testseg")

__declspec (allocate ("testseg")) class __declspec (novtable) Test {
    virtual void func (void);
} test1; 

This defines a class Test with one virtual function but which is to have no virtual function table. The fragment simultaneously declares an instance of Test and directs that this instance be stored in a particular section.

Practical value for the class and for its instantiation would come from giving Test other members and using it as a base class for two or more derived classes. The virtual members of Test have the merit of ensuring that the derived classes have at least these functions in common. As long as all the virtual functions defined for Test are called only through the derived classes, a virtual function table for Test is redundant and the compiler may as well be told of this intention. (It can then eliminate the table and simplify the Test constructor.) Though Test is not wanted for its virtual functions, the other members of Test may be useful enough to make Test worth instantiating in its own right, but perhaps the need is limited and the careful programmer decides to place instances of Test in memory alongside other infrequently used data. Thus does the example become plausible.

Now suppose that the programmer has a weak understanding of the relevant syntax (as is also plausible, without insult, since the product documentation hardly touches this syntax) and has placed both the __declspec modifiers together, after class:

class __declspec (allocate ("testseg")) __declspec (novtable) Test {        // C4038

This attracts warning C4038 because allocate is now an ambient modifier but is illegal as one. The warning message will cite both allocate and novtable as illegal.