/determine_c_or_cpp

Determine programatically C from C++ as well as various versions

determine_c_or_cpp

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 and 0 respetively if stdbool.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 and 1 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;
}