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 CC++ 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 CC++20 introduced designated initializers to C++, but with strict limitations:
- Field designators must appear in the exact order of their declaration within the class or struct.
- 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.