Quelles différences, le cas échéant, entre C ++ 03 et C ++ 11 peuvent être détectées au moment de l'exécution?

116

Il est possible d'écrire une fonction qui, lorsqu'elle est compilée avec un compilateur C retournera 0, et lorsqu'elle sera compilée avec un compilateur C ++, retournera 1 (la solution triviale avec #ifdef __cplusplusn'est pas intéressante).

Par exemple:

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

Bien sûr, ce qui précède ne fonctionnera que si ce sizeof (char)n'est pas la même chose quesizeof (int)

Une autre solution plus portable est quelque chose comme ceci:

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

Je ne sais pas si les exemples sont corrects à 100%, mais vous comprenez l'idée. Je pense qu'il existe également d'autres façons d'écrire la même fonction.

Quelles différences, le cas échéant, entre C ++ 03 et C ++ 11 peuvent être détectées au moment de l'exécution? En d'autres termes, est-il possible d'écrire une fonction similaire qui retournerait une valeur booléenne indiquant si elle est compilée par un compilateur C ++ 03 conforme ou un compilateur C ++ 11?

bool isCpp11()
{ 
    //???
} 
Armen Tsirunyan
la source
10
Et quel est l'intérêt de cet exercice? Premièrement, vous avez une macro, et deuxièmement, il faudra plusieurs années avant que les compilateurs ne commencent à implémenter toutes les fonctionnalités de C ++ 0x, en attendant, ce sera un mélange. Le seul test raisonnable est donc le compilateur d'une macro de version.
Gene Bushuyev
4
Cela correspond pas à une vraie question mais il semble trop intéressant de suivre les règles!
David Heffernan
4
@Gene et al: Est-ce que vous évaluez toutes les questions qui sont intéressantes mais vous ne voyez pas le "point" pragmatique?
Armen Tsirunyan
2
«Nous nous attendons à ce que les réponses impliquent généralement des faits, des références ou une expertise spécifique». Je pense que cette question répond à ces attentes, votez pour la réouverture.
Karel Petranek
6
@sixlettervariables: s'il est certainement possible d'argumenter que le phrasé pourrait être meilleur, il me semble que la notion fondamentale de la question (quelles différences, le cas échéant, entre C ++ 03 et C ++ 0x peuvent être détectées à l'exécution- temps?) est parfaitement légitime. Étant donné que le code doit être compilé et exécuté dans les deux cas, il pourrait également être formulé comme traitant des changements de rupture dans C ++ 0x. Cela me semble être une question parfaitement légitime à poser également.
Jerry Coffin le

Réponses:

108

Langue de base

Accéder à un énumérateur en utilisant :::

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);
}

Vous pouvez également abuser des nouveaux mots clés

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

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

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

De plus, le fait que les littéraux de chaîne ne se convertissent plus en char*

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

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

Je ne sais pas dans quelle mesure vous avez des chances que cela fonctionne sur une implémentation réelle. Celui qui exploiteauto

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

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

Ce qui suit est basé sur le fait qu'il operator int&&s'agit d'une fonction de conversion int&&en C ++ 0x, et d'une conversion en intsuivi de logique et 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);
}

Ce cas de test ne fonctionne pas pour C ++ 0x dans GCC (ressemble à un bogue) et ne fonctionne pas en mode C ++ 03 pour clang. Un clang PR a été déposé .

Le traitement modifié des noms de classe injectés des modèles 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();
}

Un couple de "détecter s'il s'agit de C ++ 03 ou C ++ 0x" peut être utilisé pour illustrer les changements de rupture. Ce qui suit est un cas de test modifié, qui a initialement été utilisé pour démontrer un tel changement, mais est maintenant utilisé pour tester C ++ 0x ou 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); }

Bibliothèque standard

Détecter le manque de operator void*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
la source
1
Agréable. Je peux confirmer que cette solution fonctionne ici avec g ++ (GCC) 4.6.0, à la fois avec et sans -std = c ++ 0x.
Alexander
2
Cela revient truepour MSVC 2005 et une erreur de compilation dans MSVC 2003.
Anthony Williams
1
Oh mon Dieu, ils ont cassé la compatibilité ascendante!
avakar
14
@Johannes: C'est le plus amusant que vous ayez eu depuis des semaines, n'est-ce pas? ; -]
ildjarn
4
Je trouve tout cela très intéressant, mais je pense que le plus intelligent est le (...)vs (char*)calls. J'aime beaucoup ça!
corsiKa
44

Je me suis inspiré de Quels changements de rupture sont introduits dans C ++ 11? :

#define u8 "abc"

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

Ceci est basé sur les nouveaux littéraux de chaîne qui ont priorité sur l'expansion de macro.

Karel Petranek
la source
1
+1: Très intéressant, en effet, mais rompt techniquement l'exigence de ne pas utiliser le préprocesseur. Mais la restriction ne visait pas à rejeter de si belles réponses :)
Armen Tsirunyan
1
Eh bien, si vous suivez la fonction avec un #undef u8alors l'utilisation du préprocesseur n'est observable que si votre programme a une macro précédemment définie nommée u8(boooo). Si c'est un problème réel, cela peut toujours être contourné en utilisant des pragmas / appels de macro push / pop spécifiques à l'implémentation (la plupart des implémentations en ont, je crois).
James McNellis
3
Un argument assez raisonnable est que sur un système C ++ 03, quelqu'un pourrait #define u8 pour fournir des capacités C ++ 0x simulées. Pourtant, j'aime vraiment la réponse.
Christopher Smith
1
vous pouvez simplement déplacer cette fonction isCpp0x vers une unité de traduction séparée pour que cette macro n'affecte pas les autres codes.
unkulunkulu
1
Je pense qu'il y a une différence entre l'utilisation du préprocesseur pour s'appuyer sur le compilateur définissant une valeur de macro et l'utilisation du préprocesseur pour détecter les fonctionnalités réelles du langage. C'est pourquoi je ne pense pas que cette réponse triche.
Courses de légèreté en orbite le
33

Que diriez-vous d'un chèque en utilisant les nouvelles règles de >>fermeture des modèles:

#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;
}

Alternativement, une vérification rapide pour 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
la source
6
+1 Une bonne idée en effet :) Cependant, en pratique, cela rompra avec Visual C ++ 2005/2088 qui ne prend pas en charge C ++ 0x mais autorise >> à être utilisé dans les modèles de la manière C ++ 0x.
Karel Petranek
4
Oooh; J'aime l'abus ADL! Cependant, une implémentation C ++ 03 conforme ne pourrait-elle pas avoir une fonction nommée std::move?
James McNellis
1
@FredOverflow: Je ne dérangerais pas. L'interface utilisateur est nul!
Courses de légèreté en orbite le
16

Contrairement au C ++ précédent, C ++ 0x permet de créer des types de référence à partir de types de référence si ce type de référence de base est introduit, par exemple, via un paramètre de modèle:

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

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

Une transmission parfaite se fait malheureusement au prix de la rupture de la rétrocompatibilité.

Un autre test pourrait être basé sur des types locaux désormais autorisés comme arguments de modèle:

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
la source
J'aurais peut-être dû en faire une classe de traits;)
uwedolinsky
1
+1 Bonne idée, je ne sais pas si isC++0xc'est un identifiant C ++ valide;)
Karel Petranek
1
Quelle est une bonne référence pour la référence à partir de l'inférence de références?
Kerrek SB
@ kerrek-sb: Le projet en parle dans 8.3.2.6 (Références)
uwedolinsky
15

Ce n'est pas un exemple tout à fait correct, mais c'est un exemple intéressant qui peut distinguer C vs C ++ 0x (c'est cependant C ++ 03 invalide):

 int IsCxx03()
 {
   auto x = (int *)0;
   return ((int)(x+1) != 1);
}
Adam Rosenfield
la source
10
Techniquement, cela dépend d' sizeof(int) != 1être vrai. Sur un système 0x avec des chars exceptionnellement grands , les résultats pourraient être les mêmes. Encore un truc sympa, cependant.
Dennis Zickefoose
@Dennis - charest toujours un octet
Node
4
@Node: un octet n'est pas toujours 8 bits.
Alexandre C.
2
@Node sizeof(char)sera toujours 1, par définition. Mais CHAR_BIT(défini dans limits.h) est autorisé à être supérieur à 8. En conséquence, les deux charet intpourraient avoir 32 bits, auquel cas sizeof(int) == 1(et CHAR_BIT == 32).
Sjoerd
12

De cette question :

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.
la source
Je me suis demandé comment cela pouvait fonctionner; après l'avoir essayé, c'est clair maintenant: il y a un petit bug. cela fonctionne si vous changez la dernière ligne en:bool is_cpp0x = !test[0].flag;
awx
1
plausible: les constructions par défaut de C ++ 0x Talors que la copie de C ++ 03 construit deT()
awx
9

Bien que pas si concis ... Dans le C ++ actuel, le nom du modèle de classe lui-même est interprété comme un nom de type (pas un nom de modèle) dans la portée de ce modèle de classe. D'autre part, le nom de modèle de classe peut être utilisé comme nom de modèle dans 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
la source
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
la source
NB techniquement, cela teste la bibliothèque standard et non le compilateur, et bien qu'il soit valide C ++ 03 et C ++ 0x valide, ce n'est pas valide C ++ 98, donc avec quelques ajustements pourraient être faits pour détecter un C ++ 98 / C ++ 03 / C ++ 0x stdlib
Jonathan Wakely