¿Qué diferencias, si las hay, entre C ++ 03 y C ++ 11 se pueden detectar en tiempo de ejecución?

116

Es posible escribir una función que, cuando se compila con un compilador de C, devolverá 0, y cuando se compila con un compilador de C ++, devolverá 1 (la solución trivial con #ifdef __cplusplusno es interesante).

Por ejemplo:

int isCPP()
{
    return sizeof(char) == sizeof 'c';
}

Por supuesto, lo anterior funcionará solo si sizeof (char)no es lo mismo quesizeof (int)

Otra solución más portátil es algo como esto:

int isCPP()
{
    typedef int T;
    {
       struct T 
       {
           int a[2];
       };
       return sizeof(T) == sizeof(struct T);
    }
}

No estoy seguro de si los ejemplos son 100% correctos, pero entiendes la idea. Creo que también hay otras formas de escribir la misma función.

¿Qué diferencias, si las hay, entre C ++ 03 y C ++ 11 se pueden detectar en tiempo de ejecución? En otras palabras, ¿es posible escribir una función similar que devuelva un valor booleano que indique si está compilada por un compilador C ++ 03 conforme o un compilador C ++ 11?

bool isCpp11()
{ 
    //???
} 
Armen Tsirunyan
fuente
10
¿Y cuál es el objetivo de este ejercicio? En primer lugar, tiene una macro y, en segundo lugar, pasarán varios años antes de que los compiladores comiencen a implementar todas las características de C ++ 0x, mientras tanto, será una mezcla. Entonces, la única prueba razonable es el compilador de una versión macro.
Gene Bushuyev
4
Esto se ajusta al proyecto de ley de no una pregunta real, ¡pero parece demasiado interesante para seguir las reglas!
David Heffernan
4
@Gene et al: ¿Usted rechaza todas las preguntas que son interesantes pero no ve el "punto" pragmático?
Armen Tsirunyan
2
"Esperamos que las respuestas generalmente involucren hechos, referencias o experiencia específica". Creo que esta pregunta cumple con estas expectativas, vote por la reapertura.
Karel Petranek
6
@sixlettervariables: aunque ciertamente está abierto a argumentar que la redacción podría ser mejor, me parece que la noción fundamental de la pregunta (qué diferencias, si las hay, entre C ++ 03 y C ++ 0x se pueden detectar al ejecutar- ¿tiempo?) es perfectamente legítimo. Dado que el código tiene que compilarse y ejecutarse en ambos, también podría expresarse como acerca de los cambios importantes en C ++ 0x. A mí también me parece una pregunta perfectamente legítima.
Jerry Coffin

Respuestas:

108

Lenguaje principal

Accediendo a un enumerador usando :::

template<int> struct int_ { };

template<typename T> bool isCpp0xImpl(int_<T::X>*) { return true; }
template<typename T> bool isCpp0xImpl(...) { return false; }

enum A { X };
bool isCpp0x() {
  return isCpp0xImpl<A>(0);
}

También puede abusar de las nuevas palabras clave

struct a { };
struct b { a a1, a2; };

struct c : a {
  static b constexpr (a());
};

bool isCpp0x() {
  return (sizeof c::a()) == sizeof(b);
}

Además, el hecho de que los literales de cadena ya no se conviertan en char*

bool isCpp0xImpl(...) { return true; }
bool isCpp0xImpl(char*) { return false; }

bool isCpp0x() { return isCpp0xImpl(""); }

Sin embargo, no sé qué tan probable es que esto funcione en una implementación real. Uno que explotaauto

struct x { x(int z = 0):z(z) { } int z; } y(1);

bool isCpp0x() {
  auto x(y);
  return (y.z == 1);
}

Lo siguiente se basa en el hecho de que operator int&&es una función de conversión a int&&en C ++ 0x, y una conversión a intseguida de lógica y en C ++ 03

struct Y { bool x1, x2; };

struct A {
  operator int();
  template<typename T> operator T();
  bool operator+();
} a;

Y operator+(bool, A);

bool isCpp0x() {
  return sizeof(&A::operator int&& +a) == sizeof(Y);
}

Ese caso de prueba no funciona para C ++ 0x en GCC (parece un error) y no funciona en el modo C ++ 03 para clang. Se ha archivado un clang PR .

El tratamiento modificado de los nombres de clases inyectados de plantillas en C ++ 11:

template<typename T>
bool g(long) { return false; }

template<template<typename> class>
bool g(int) { return true; }

template<typename T>
struct A {
  static bool doIt() {
    return g<A>(0);
  }
};

bool isCpp0x() {
  return A<void>::doIt();
}

Se pueden usar un par de "detectar si esto es C ++ 03 o C ++ 0x" para demostrar cambios importantes. El siguiente es un caso de prueba modificado, que inicialmente se usó para demostrar tal cambio, pero ahora se usa para probar C ++ 0x o C ++ 03.

struct X { };
struct Y { X x1, x2; };

struct A { static X B(int); };
typedef A B;

struct C : A {
  using ::B::B; // (inheriting constructor in c++0x)
  static Y B(...);
};

bool isCpp0x() { return (sizeof C::B(0)) == sizeof(Y); }

Biblioteca estándar

Detectando la falta de operator void*en C ++ 0x 'std::basic_ios

struct E { E(std::ostream &) { } };

template<typename T>
bool isCpp0xImpl(E, T) { return true; }
bool isCpp0xImpl(void*, int) { return false; }

bool isCpp0x() {
  return isCpp0xImpl(std::cout, 0);
}
Johannes Schaub - litb
fuente
1
Agradable. Puedo confirmar que esta solución funciona aquí con g ++ (GCC) 4.6.0, con y sin -std = c ++ 0x.
Alexander
2
Esto regresa truepara MSVC 2005 en adelante, y un error de compilación en MSVC 2003.
Anthony Williams
1
¡Dios mío, rompieron la compatibilidad con versiones anteriores!
Avakar
14
@Johannes: Esto es lo más divertido que has tenido en semanas, ¿no? ; -]
ildjarn
4
Encuentro todo esto muy interesante, pero creo que lo más inteligente son las llamadas (...)vs. (char*)¡Realmente me gusta eso!
corsiKa
44

Me inspiré en ¿Qué cambios importantes se introducen en C ++ 11? :

#define u8 "abc"

bool isCpp0x() {
   const std::string s = u8"def"; // Previously "abcdef", now "def"
   return s == "def";
}

Esto se basa en los nuevos literales de cadena que tienen prioridad sobre la expansión de macros.

Karel Petranek
fuente
1
+1: Muy interesante, de hecho, pero técnicamente rompe el requisito de no usar el preprocesador. Pero la restricción no tenía la intención de descartar respuestas tan agradables :)
Armen Tsirunyan
1
Bueno, si sigue la función con a, #undef u8entonces el uso del preprocesador solo es observable si su programa tiene una macro previamente definida llamada u8(boooo). Si eso es una preocupación real, aún se puede solucionar usando pragmas / llamadas de macro push / pop específicos de la implementación (la mayoría de las implementaciones tienen estos, creo).
James McNellis
3
Un argumento bastante razonable es que en un sistema C ++ 03 alguien podría # definir u8 para proporcionar capacidades C ++ 0x simuladas. Aún así, me gusta mucho la respuesta.
Christopher Smith
1
puede mover esta función isCpp0x a una unidad de traducción separada para que esta macro no afecte a otro código.
unkulunkulu
1
Creo que hay una diferencia entre usar el preprocesador para confiar en que el compilador establezca algún valor macro y usar el preprocesador para detectar características reales del lenguaje. Por eso no creo que esta respuesta sea trampa.
Lightness Races in Orbit
33

¿Qué tal un cheque usando las nuevas reglas para >>cerrar plantillas?

#include <iostream>

const unsigned reallyIsCpp0x=1;
const unsigned isNotCpp0x=0;

template<unsigned>
struct isCpp0xImpl2
{
    typedef unsigned isNotCpp0x;
};

template<typename>
struct isCpp0xImpl
{
    static unsigned const reallyIsCpp0x=0x8000;
    static unsigned const isNotCpp0x=0;
};

bool isCpp0x() {
    unsigned const dummy=0x8000;
    return isCpp0xImpl<isCpp0xImpl2<dummy>>::reallyIsCpp0x > ::isNotCpp0x>::isNotCpp0x;
}

int main()
{
    std::cout<<isCpp0x()<<std::endl;
}

Alternativamente, una comprobación rápida de std::move:

struct any
{
    template<typename T>
    any(T const&)
    {}
};

int move(any)
{
    return 42;
}

bool is_int(int const&)
{
    return true;
}

bool is_int(any)
{
    return false;
}


bool isCpp0x() {
    std::vector<int> v;
    return !is_int(move(v));
}
Anthony Williams
fuente
6
+1 Una idea genial :) Sin embargo, en la práctica esto romperá con Visual C ++ 2005/2088 que no es compatible con C ++ 0x pero permite >> ser usado en plantillas de la forma C ++ 0x.
Karel Petranek
4
Oooh; ¡Me gusta el abuso de ADL! Sin embargo, ¿no podría una implementación conforme a C ++ 03 tener una función nombrada std::move?
James McNellis
1
@FredOverflow: No me molestaría. ¡La interfaz de usuario apesta!
Lightness Races in Orbit
16

A diferencia de C ++ anterior, C ++ 0x permite crear tipos de referencia a partir de tipos de referencia si ese tipo de referencia base se introduce mediante, por ejemplo, un parámetro de plantilla:

template <class T> bool func(T&) {return true; } 
template <class T> bool func(...){return false;} 

bool isCpp0x() 
{
    int v = 1;
    return func<int&>(v); 
}

El reenvío perfecto tiene el precio de romper la compatibilidad con versiones anteriores, desafortunadamente.

Otra prueba podría basarse en tipos locales ahora permitidos como argumentos de plantilla:

template <class T> bool cpp0X(T)  {return true;} //cannot be called with local types in C++03
                   bool cpp0X(...){return false;}

bool isCpp0x() 
{
   struct local {} var;
   return cpp0X(var);
}
uwedolinsky
fuente
Quizás debería haber convertido eso en una clase de rasgo;)
uwedolinsky
1
+1 Buena idea, solo no estoy seguro de si isC++0xes un identificador C ++ válido;)
Karel Petranek
1
¿Qué es una buena referencia para la referencia a partir de la inferencia de referencias?
Kerrek SB
@ kerrek-sb: El borrador habla de esto en 8.3.2.6 (Referencias)
uwedolinsky
15

Este no es un ejemplo del todo correcto, pero es un ejemplo interesante que puede distinguir C de C ++ 0x (aunque C ++ 03 no es válido):

 int IsCxx03()
 {
   auto x = (int *)0;
   return ((int)(x+1) != 1);
}
Adam Rosenfield
fuente
10
Técnicamente, esto se basa en sizeof(int) != 1ser verdad. En un sistema 0x con chars excepcionalmente grandes , los resultados podrían ser los mismos. Sin embargo, sigue siendo un buen truco.
Dennis Zickefoose
@Dennis - charaunque siempre es un byte
Nodo
4
@Nodo: un byte no siempre es de 8 bits.
Alexandre C.
2
@Node sizeof(char)siempre será 1, por definición. Pero CHAR_BIT(definido en limits.h) se permite que sea más de 8. Como resultado, ambos chary intpodrían tener 32 bits, en cuyo caso sizeof(int) == 1(y CHAR_BIT == 32).
Sjoerd
12

De esta pregunta :

struct T
{
    bool flag;
    T() : flag(false) {}
    T(const T&) : flag(true) {}
};

std::vector<T> test(1);
bool is_cpp0x = !test[0].flag;
Alexandre C.
fuente
Me preguntaba cómo podría funcionar esto; habiéndolo probado, ahora está claro: hay un pequeño error. funciona si cambia la última línea a:bool is_cpp0x = !test[0].flag;
awx
1
plausible: construcciones predeterminadas de C ++ 0x Tmientras que C ++ 03 copia construcciones deT()
awx
9

Aunque no es tan conciso ... En C ++ actual, el nombre de la plantilla de clase en sí se interpreta como un nombre de tipo (no un nombre de plantilla) en el alcance de esa plantilla de clase. Por otro lado, el nombre de la plantilla de clase se puede usar como un nombre de plantilla en C ++ 0x (N3290 14.6.1 / 1).

template< template< class > class > char f( int );
template< class > char (&f(...))[2];

template< class > class A {
  char i[ sizeof f< A >(0) ];
};

bool isCpp0x() {
  return sizeof( A<int> ) == 1;
}
Ise Wisteria
fuente
9
#include <utility>

template<typename T> void test(T t) { t.first = false; }

bool isCpp0x()
{
   bool b = true;
   test( std::make_pair<bool&>(b, 0) );
   return b;
}
Jonathan Wakely
fuente
NB técnicamente, esto prueba la biblioteca estándar, no el compilador, y aunque es C ++ 03 válido y C ++ 0x válido, no es C ++ 98 válido, por lo que con algunos ajustes se podrían hacer para detectar un C ++ 98 / C ++ 03 / C ++ 0x stdlib
Jonathan Wakely