Determine programatically C from C++ as well as various versions
-
In C the comma operator forces an array-to-pointer conversion while in C++ the result of the comma operator is the same value type as the RHS:
int f() { char arr[100]; return sizeof(0, arr); // returns 8 in C and 100 in C++ }
I used this example in a #CppPolls
-
C uses struct, union and enum tags as a primitive form of namespace see reference. So the following code will give sizeof int for C and sizeof struct T for C++
#include <stdio.h> extern int T; int size(void) { struct T { int i; int j; }; return sizeof(T); } int main() { printf( "%d\n", size() ); // sizeof(int) in C and sizeof(struct T) in C++ return 0 ; }
-
Character literals are treated different in C and C++. An integer character constant has type int in C and a character lterals with a single char has type char. So the following code will likely produce 4 for C and 1 for C++
#include<stdio.h> int main() { printf("%zu",sizeof('a')); // sizeof(int) in C and sizeof(char) in C++ return 0; }
-
C90 does not have // style comments, we can differentiate C90 from C99, C11 and C++:
#include <stdio.h> int main() { int i = 2 //**/2 ; printf( "%d\n", i ) ; // 1 in C90 // 2 in C99, C11 and C++ return 0 ; }
-
C99 if statement implicitly defines a block scope above and below it (h/t this tweet:
#include <stdio.h> struct s { long long m; }; int main(void) { int x = 0; if (x) x = sizeof(struct s { char c;}); else printf("%zu\n", sizeof (struct s)); // 1 in C89 // 8 in C99, assuming LP64 return 0; }
-
K&R C used the unsigned preserving approach to integer promotions, which means when we mix unsigned and signed integers they are promoted to unsigned. Where as ANSI C and C++ use value preserving approach, which means when mixing signed and unsigned integers the are promoted to int if int can represent all values.
#include <stdio.h> int main() { if( (unsigned char)1 > -1 ) { // false for K&R C // true for ANSI C and C++ printf("yes\n" ) ; } }
-
In C++ true and false are treated differently during preprocessing then the rest of the keywords and are not replaced with 0 and subepxressions of type bool are subject to integral promotions. In C true and false are not keywords but are macros with values
1
and0
respetively ifstdbool.h
is included:#if true #define ISC 0 #else #define ISC 1 #endif
-
The Stackoverflow question Can C++ code be valid in both C++03 and C++11 but do different things? contains several examples of code that generate different results for C++03 and C++11:
-
New kinds of string literals [...] Specifically, macros named R, u8, u8R, u, uR, U, UR, or LR will not be expanded when adjacent to a string literal but will be interpreted as part of the string literal. The following code will print abcdef in C++03 and def in C++11:
#include <cstdio> #define u8 "abc" int main() { const char *s = u8"def"; printf( "%s\n", s ) ; // abcdef in C++03 and def in C++11 return 0; }
-
Using >> to close multiple templates is no longer ill-formed but can lead to code with different results in C++03 and C+11.
#include <iostream> template<int I> struct X { static int const c = 2; }; template<> struct X<0> { typedef int c; }; template<typename T> struct Y { static int const c = 3; }; static int const c = 4; int main() { std::cout << (Y<X<1> >::c >::c>::c) << '\n'; // 0 in C++03 // 0 in C++11 std::cout << (Y<X< 1>>::c >::c>::c) << '\n'; // 3 in C++03 // 0 in C++11 }
-
-
We can identify if we are C++17 and greater by attempting to use trigrpahs, which were removed in C++17:
int iscpp17orGreater(){ //??/ return 1; // trigraph on previous line will be ignored in C++17 return 0; // trigraphs two lines above will form continuation and comment out the previous line }
-
How to tell C++20 from previous versions of C++. In C++20 introduced the spaceship operator <=> (🛸) and part of this change was rewritten candidates for equality and others see [over.match.oper]p3.4. The following code will print
0
in C++20 and1
in previous versions of C++:#include <iostream> struct A { operator int() {return 1;} }; bool operator==(A, int){return 0;} What is the result of: int main() { std::cout <<(1 == A{})<<"\n"; // 0 in C++20 // 1 in previous versions of C++ }
-
C++14 added the single quote as a digit seperator this means that prior to C++14 a comma in single quotes would be treated as a seperator in macros but C++14 onwards would not. Assuming we can indentify C++03, C++17 and C++20 we can use the following method to then differentiate C++11 and C++14.
#define M(x, ...) __VA_ARGS__ int x[2] = { M(1'2,3'4, 5) }; // macro CPP03 is 1 is we are C++03 // macro CPP17 is 1 if we are C++17 // macro CPP20 is 1 is we are C++20 int CPP14 = x[0] == 34 && !CPP17 && !CPP20; int CPP11 = x[0] == 5 && !CPP03 ;
-
We can combine this together into one program to detect both C and C++ and their respective versions. Note: K&R C did not have const so this needs some modifying to be portable for that case:
#if true
#define ISC 0
#else
#define ISC 1
#endif
#include <stdio.h>
#define u8 "abc"
const int KandRC = (((unsigned char)1 > -1) == 0);
const int comment = 2 //**/2
;
const int C90 = (comment == 1? 1 : 0) && ISC;
const int C11 =
ISC && (sizeof(u8"def") == 4);
const int C99 = !C11 && !C90 && ISC;
#if ISC
int CPP03 = 0;
int CPP11 = 0;
int CPP14 = 0;
int CPP17 = 0;
int CPP20 = 0;
#else
template<int I> struct X {
static int const c = 2;
};
template<> struct X<0> {
typedef int c;
};
template<typename T> struct Y {
static int const c = 3;
};
static int const c = 4;
int CPP03 = (Y<X< 1>>::c >::c>::c == 3);
struct A {
operator int() {return 1;}
};
bool operator==(A, int){return 0;}
int iscpp17(){
//??/
return 1; // trigraph on previous line will be ignored in C++17
return 0; // trigraphs two lines above will form continuation and comment out the previous line
}
int isCPP20() {return !(1 == A());}
int CPP20 = isCPP20();
int CPP17 = iscpp17() && !CPP20;
#define M(x, ...) __VA_ARGS__
int x[2] = { M(1'2,3'4, 5) };
int CPP14 = x[0] == 34 && !CPP17 && !CPP20;
int CPP11 = x[0] == 5 && !CPP03 ;
#endif
int main()
{
printf( "%s\n", (ISC ? "C" : "C++"));
printf( "%s\n", (CPP03 ? "C++03" : "not C++03"));
printf( "%s\n", (CPP11 ? "C++11" : "not C++11"));
printf( "%s\n", (CPP14 ? "C++14" : "not C++14"));
printf( "%s\n", (CPP17 ? "C++17" : "not C++17"));
printf( "%s\n", (CPP20 ? "C++20" : "not C++20"));
printf( "%s\n", (C90 ? "C90" : "not c90"));
printf( "%s\n", (C99 ? "C99" : "not c99"));
printf( "%s\n", (C11 ? "C11" : "not c11"));
printf( "%s\n", (KandRC ? "K&R C" : "not K&R C"));
return 0;
}