Valid C Syntax That Is Invalid in Modern C++

While C++ originated as an extension of C, it has evolved into a fundamentally distinct language with stricter type safety, different scoping rules, and unique keywords. Consequently, several common syntax constructs and idiomatic patterns that compile cleanly in standard C will fail to compile in modern C++. This article outlines the most significant C syntax constructs that are invalid in modern C++, including implicit pointer conversions, variable-length arrays, unprototyped functions, and restrictive designated initializers.

Implicit Conversion from void*

In C, a void* pointer implicitly converts to any other object pointer type. This pattern is ubiquitous when allocating memory with malloc.

int *ptr = malloc(sizeof(int) * 10);

In modern C++, implicit conversion from void* to a typed pointer is forbidden to preserve type safety. Compiling the snippet above in C++ results in a compilation error. C++ requires an explicit cast or the use of modern memory management:

int *ptr = static_cast<int*>(malloc(sizeof(int) * 10)); // Explicit cast
// Or preferably in C++:
int *ptr = new int[10];

Functions with Empty Parameter Lists

In C, a function declaration with empty parentheses indicates that the function takes an unspecified number of arguments rather than zero arguments.

void func(); // In C: takes an unspecified number of parameters

int main(void) {
    func(1, 2, 3); // Valid in C (though deprecated in modern revisions)
    return 0;
}

In C++, an empty parameter list void func() is strictly equivalent to void func(void). Passing arguments to a function declared with () triggers a compiler error in C++ because the signature expects zero parameters.

Variable-Length Arrays (VLAs)

Introduced as a standard feature in C99, Variable-Length Arrays allow array dimensions to be determined at runtime on the stack.

void allocate(int n) {
    int arr[n]; // Valid in C99/C11
}

Standard modern C++ does not support VLAs. Array bounds in C++ must be compile-time constant expressions. While some compilers provide VLAs as an extension, standard-compliant C++ code rejects this syntax, requiring alternatives like std::vector or dynamic allocation.

Implicit int and Unchecked Enum Conversions

In C, integer types can be implicitly converted into enumeration types without an explicit cast.

enum Color { RED, GREEN, BLUE };
enum Color c = 1; // Valid in C

C++ enforces strict type boundaries around enumerations. An integer literal or variable cannot be assigned to an enum without an explicit cast:

Color c = static_cast<Color>(1); // Required in C++

Designated Initializers with Out-of-Order Fields or Arrays

C99 introduced designated initializers, permitting structure members to be initialized in any order and allowing array indexing syntax.

struct Point { int x; int y; };
struct Point p = { .y = 20, .x = 10 }; // Out of order: Valid in C

int arr[5] = { [2] = 5, [4] = 9 };     // Array designation: Valid in C

C++20 introduced designated initializers to C++, but with strict limitations:

  1. Field designators must appear in the exact order of their declaration within the class or struct.
  2. Array index designators (e.g., [2] = 5) are completely unsupported.

Both examples above produce compiler errors in modern C++.

Flexible Array Members

C99 formally supports flexible array members, where the last element of a structure has an incomplete array type to allow dynamic tail allocation.

struct Packet {
    int length;
    char data[]; // Flexible array member: Valid in C
};

Standard C++ does not support flexible array members. Using an empty bracket declaration at the end of a struct or class is non-standard in C++ and results in an error under strict standard conformance flags.

Using C++ Keywords as Identifiers

C++ reserves dozens of keywords that are regular identifiers in C. Variable or function names using terms like new, delete, class, template, virtual, or public are valid C syntax but invalid C++.

int new = 5;
void export(void);

Both declarations fail to compile in C++ because the identifiers clash with core language keywords.

Tentative Definitions

In C, a global variable can be declared multiple times without the extern specifier in the same translation unit, provided only one instance includes an initializer.

int count;
int count; // Valid in C (treated as a tentative definition)

C++ strictly enforces the One Definition Rule (ODR). In C++, every declaration of this form is treated as a definition, leading to a "redefinition of 'count'" compilation error.

Jumping Over Variable Declarations with goto

C allows a goto statement to jump over variable declarations, provided the variable is not a VLA.

void test(void) {
    goto label;
    int x = 10; // Skipped
label:
    return;
}

In C++, jumping over a variable declaration with an initializer—or jumping over any non-trivial object construction—into a scope where that variable would be visible is strictly illegal and produces a compiler error.