Two Core C99 Features that C++11 Lacks

It's too late now to make changes to the C++11 FDIS, and yet it's still worth looking at two core-language features that were added to C99 and never made it into C++.

restrict Pointers

The C99 keyword restrict is a pointer qualifier. It means that for the lifetime of the qualified pointer, only it or a value directly derived from it (such as p+1, *p etc.​) will be used to access the object to which it points. Such a guarantee about a given pointer enables certain caching optimizations and minimizes the effects of pointer aliasing. The following example demonstrates this:

void update(int *p1, int *p2, int *val)
{
*p1 += *val;
*p2 += *val;
}

The three pointer arguments aren't restrict-qualified. Therefore, the compiler has to assume that they might refer to the same memory location. Consequently, it will be forced to generate less optimal code. By contrast, if the programmer applies restrict to each of the three pointer arguments, as in:

the compiler can assume that p1, p2 and val point to different locations, and updating one pointer will not affect any other pointer. Under this assumption, the compiler can optimize the generated code.

restrict has been around since 1999, and possibly earlier when the first C99 drafts started circulating. Some of the committee members I've spoken to agree that from their experience, restrict can improve performance quite noticeably. In fact, I believe that there's a consensus about restrict's performance benefits, after more than 10 years of real-world experience. And yet, C++ doesn't support restrict pointers. No one has come up with a convincing argument against restrict in C++. I can only assume that C++ lacks this feature for the wrong reasons: lack of interest, lack of time and maybe some tug of wars between the C and C++ standards committees (to be fair, there's much better collaboration between the two committees today. It wasn't always the case, though).

On a practical note, adding restrict to C++ would require additional research and testing. Should it apply to references as well? Should it apply to both lvalue references and rvalue references? How does restrict interact with concurrency-related features and the new memory model of C+11? I doubt that anyone has had the time to explore these issues yet because the current specification of C++11 rvalue references was finalized very lately. However, it might be a good idea to consider it, at least for the next C++ standard.

Designated Initializers

One of the most convenient core-language features of C99 is called designated initializers. They let you initialize aggregates (structs, unions and arrays) partially, specifying only the members/elements that you want initialized, while skipping the rest (these are default initialized). Consider:

The braces include designated initializers for the data member a and c. Since b has no explicit initializer, it's zero-initialized implicitly. Notice that unlike class member initializers in C++, designated initializers let you specify ad-hoc initializers for a given object. A different instance of the same struct might use different initializers:

struct S r={.a=5}; //C99, b and c are zero initialized

Similarly, you can use the same technique to initialize a union:

union U//C99 only
{
int a;
double d;
char * c;
} t = { .c =NULL};

You initialize arrays like this:

int arr[4] = { [2] = 3, [1] = 9 };//C99 only

The elements arr[2] and arr[1] are initialized to 3 and 9, respectively. The remaining elements are zero-initialized.

Designated Initializers and C++

Sadly, designated initializers didn't make it into C++11 either. Again, I can't think of a good reason for that. This issue has been raised several times on the committee's reflectors (list servers) and has either been greeted with cynicism or completely ignored.

One could argue -- as some have -- that C++ doesn't need this feature because it rarely uses POD aggregates. Instead of POD structs you'd normally use a class with proper constructors, and instead of arrays you'd better use std::vector and std::array. This may be true to some extent but there are at least three reasons for supporting designated initializers in C++ in spite of that:

Computability with C. Like it or not, C is a very popular programming language these days and it will surely remain so in the foreseeable future. The lack of support for designated initializers in C++ breaks compatibility with C programs that use this feature.

Designated Initializers

C99 is old news. The new C standard, C1x, brings even more new features that C++11 won't support. Letting the gaping chasms between the two languages expand is a strategic mistake. The efforts needed to make C++ more compatible with C are minimal -- just a few months of work. By contrast, the damage of the growing incompatibility between the two languages is significant:

Breaking compatibility. As I've explained above, the assumption that C++ is a superset of C and that every C program is a valid C++ program (with perhaps minimal adjustments) is gradually becoming tapering down.

Dialectal splits. When the C++ standards committee drags its feet, compiler vendors take the initiative, adding support for non-standard extensions, e.g., C99 features, to their C++ compilers. Consequently, you end up having different dialects of C++ -- one dialect per implementation.

Denial. C++ users can pretend that the aforementioned C99 aren't needed or useful in C++. That isn't the case. The fact is that these features could prove at least as useful in C++ as they are in C.