Jakie różnice, jeśli w ogóle, między C ++ 03 i C ++ 11 można wykryć w czasie wykonywania?

116

Możliwe jest napisanie funkcji, która po skompilowaniu kompilatorem C zwróci 0, a skompilowana kompilatorem C ++ zwróci 1 (trywialne rozwiązanie #ifdef __cplusplusnie jest interesujące).

Na przykład:

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

Oczywiście powyższe zadziała tylko wtedy, gdy sizeof (char)nie jest takie samo jaksizeof (int)

Innym, bardziej przenośnym rozwiązaniem jest coś takiego:

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

Nie jestem pewien, czy przykłady są w 100% poprawne, ale masz pomysł. Uważam, że są też inne sposoby na napisanie tej samej funkcji.

Jakie różnice, jeśli w ogóle, między C ++ 03 i C ++ 11 można wykryć w czasie wykonywania? Innymi słowy, czy można napisać podobną funkcję, która zwróciłaby wartość logiczną wskazującą, czy jest kompilowana przez zgodny kompilator C ++ 03 czy kompilator C ++ 11?

bool isCpp11()
{ 
    //???
} 
Armen Tsirunyan
źródło
10
Jaki jest sens tego ćwiczenia? Po pierwsze, masz makro, a po drugie zajmie to kilka lat, zanim kompilatory zaczną implementować wszystkie funkcje C ++ 0x, w międzyczasie będzie to mieszanka. Więc jedynym rozsądnym testem jest kompilator makra wersji.
Gene Bushuyev,
4
To nie jest prawdziwe pytanie, ale przestrzeganie zasad wydaje się zbyt interesujące!
David Heffernan,
4
@Gene et al: Czy odrzucasz wszystkie interesujące pytania, ale nie widzisz pragmatycznego „punktu”?
Armen Tsirunyan
2
„Oczekujemy, że odpowiedzi będą zasadniczo zawierać fakty, odniesienia lub konkretną wiedzę specjalistyczną”. Myślę, że to pytanie spełnia te oczekiwania, głosuj za ponownym otwarciem.
Karel Petranek
6
@sixlettervariables: chociaż z pewnością można spierać się, że frazowanie mogłoby być lepsze, wydaje mi się, że podstawowe pojęcie pytania (jakie różnice, jeśli w ogóle, między C ++ 03 i C ++ 0x można wykryć w run- czas?) jest całkowicie uzasadnione. Biorąc pod uwagę, że kod musi się skompilować i wykonać w obu, można go również sformułować jako opisujący istotne zmiany w C ++ 0x. Wydaje mi się, że jest to również całkowicie uzasadnione pytanie.
Jerry Coffin

Odpowiedzi:

108

Język podstawowy

Dostęp do modułu wyliczającego przy użyciu :::

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

Możesz także nadużywać nowych słów kluczowych

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

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

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

Ponadto fakt, że literały ciągów nie są już konwertowane na char*

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

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

Nie wiem jednak, jak prawdopodobne jest, że będzie to działać na prawdziwej implementacji. Taki, który wykorzystujeauto

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

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

Poniższe jest oparte na fakcie, że operator int&&jest to funkcja konwersji na int&&w C ++ 0x i konwersja na, intpo której następuje logiczna - i w 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);
}

Ten przypadek testowy nie działa dla C ++ 0x w GCC (wygląda na błąd) i nie działa w trybie C ++ 03 dla clang. Zgłoszono clang PR .

Zmodyfikowane leczenie wstrzyknięto nazw klas szablonów w 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();
}

Do zademonstrowania istotnych zmian można użyć kilku opcji „wykryj, czy jest to C ++ 03 czy C ++ 0x”. Poniżej znajduje się zmodyfikowany przypadek testowy, który początkowo był używany do zademonstrowania takiej zmiany, ale teraz jest używany do testowania pod kątem C ++ 0x lub 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); }

Biblioteka standardowa

Wykrywanie braku operator void*w 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
źródło
1
Miły. Mogę potwierdzić, że to rozwiązanie działa tutaj z g ++ (GCC) 4.6.0, zarówno z, jak i bez -std = c ++ 0x.
Alexander,
2
Powraca to truedla MSVC 2005 i nowszych, a błąd kompilacji w MSVC 2003.
Anthony Williams
1
Ojej, złamali wsteczną kompatybilność!
avakar
14
@Johannes: To najlepsza zabawa, jaką miałeś od tygodni, prawda? ; -]
ildjarn
4
Uważam, że to wszystko jest bardzo interesujące, ale myślę, że najbardziej sprytne są połączenia (...)vs. (char*)Naprawdę to lubię!
corsiKa
44

Inspirację zaczerpnąłem z Jakie przełomowe zmiany zostały wprowadzone w C ++ 11? :

#define u8 "abc"

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

Jest to oparte na nowych literałach ciągów, które mają pierwszeństwo przed interpretacją makr.

Karel Petranek
źródło
1
+1: Rzeczywiście bardzo interesujące, ale technicznie łamie wymóg nieużywania preprocesora. Ale ograniczenie nie miało na celu odrzucenia tak miłych odpowiedzi :)
Armen Tsirunyan
1
Cóż, jeśli podążasz za funkcją z a, #undef u8to użycie preprocesora jest widoczne tylko wtedy, gdy twój program ma wcześniej zdefiniowane makro o nazwie u8(boooo). Jeśli jest to prawdziwy problem, nadal można go obejść za pomocą specyficznych dla implementacji pragm / wywołań makr push / pop (wydaje mi się, że większość implementacji je ma).
James McNellis,
3
Jednym dość rozsądnym argumentem jest to, że w systemie C ++ 03 ktoś może # zdefiniować u8, aby zapewnić symulowane możliwości C ++ 0x. Mimo to naprawdę podoba mi się odpowiedź.
Christopher Smith
1
możesz po prostu przenieść tę funkcję isCpp0x do oddzielnej jednostki tłumaczeniowej, aby to makro nie wpłynęło na inny kod.
unkulunkulu
1
Myślę, że istnieje różnica między używaniem preprocesora do polegania na kompilatorze ustawiającym jakąś wartość makra a używaniem preprocesora do wykrywania rzeczywistych funkcji języka. Dlatego nie sądzę, że ta odpowiedź jest oszustwem.
Wyścigi lekkości na orbicie,
33

Co powiesz na sprawdzenie przy użyciu nowych zasad >>zamykania szablonów:

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

Alternatywnie szybkie sprawdzenie 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
źródło
6
+1 Naprawdę fajny pomysł :) Jednak w praktyce zepsuje to w przypadku Visual C ++ 2005/2088, które nie obsługują C ++ 0x, ale pozwalają na używanie >> w szablonach w sposób C ++ 0x.
Karel Petranek
4
Oooh; Podoba mi się nadużycie ADL! Jednak czy implementacja zgodna z C ++ 03 nie mogłaby mieć funkcji o nazwie std::move?
James McNellis,
1
@FredOverflow: Nie zawracałbym sobie głowy. Interfejs użytkownika jest do bani!
Wyścigi lekkości na orbicie
16

W przeciwieństwie do wcześniejszego C ++, C ++ 0x umożliwia tworzenie typów referencyjnych z typów referencyjnych, jeśli ten podstawowy typ odwołania zostanie wprowadzony za pomocą, na przykład, parametru szablonu:

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

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

Perfekcyjne przekazywanie ma niestety cenę zerwania wstecznej kompatybilności.

Inny test może opierać się na obecnie dozwolonych typach lokalnych jako argumentach szablonu:

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
źródło
Może powinienem był przekształcić to w klasę cech;)
uwedolinsky
1
+1 Niezły pomysł, nie jestem tylko pewien, czy isC++0xto prawidłowy identyfikator C ++;)
Karel Petranek
1
Jakie jest dobre odniesienie do odniesienia z wnioskowania o referencjach?
Kerrek SB
@ kerrek-sb: Projekt mówi o tym w 8.3.2.6 (Referencje)
uwedolinsky.
15

To nie jest do końca poprawny przykład, ale jest to interesujący przykład, który pozwala odróżnić C od C ++ 0x (jest to jednak nieprawidłowy C ++ 03):

 int IsCxx03()
 {
   auto x = (int *)0;
   return ((int)(x+1) != 1);
}
Adam Rosenfield
źródło
10
Technicznie rzecz biorąc, zależy to od sizeof(int) != 1prawdy. W systemie 0x z wyjątkowo dużymi chars wyniki mogą być takie same. Wciąż jednak niezła sztuczka.
Dennis Zickefoose
@Dennis - charjest jednak zawsze jednobajtowy
Węzeł
4
@ Węzeł: jeden bajt nie zawsze ma 8 bitów.
Alexandre C.
2
@ Węzeł sizeof(char)z definicji zawsze będzie miał wartość 1. Ale CHAR_BIT(zdefiniowane w limits.h) może być więcej niż 8. W rezultacie oba chari intmogą mieć 32 bity, w którym to przypadku sizeof(int) == 1(i CHAR_BIT == 32).
Sjoerd,
12

Z tego pytania :

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.
źródło
Zastanawiałem się, jak to może działać; po wypróbowaniu tego jest jasne: jest mały błąd. działa, jeśli zmienisz ostatnią linię na:bool is_cpp0x = !test[0].flag;
awx
1
plausible: domyślne konstrukcje C ++ 0x, Tpodczas gdy konstrukcje kopiujące C ++ 03 zT()
awx
9

Chociaż nie jest to takie zwięzłe ... W obecnym C ++ nazwa szablonu klasy jest interpretowana jako nazwa typu (nie nazwa szablonu) w zakresie tego szablonu klasy. Z drugiej strony nazwa szablonu klasy może być używana jako nazwa szablonu w 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
źródło
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
źródło
Uwaga, technicznie rzecz biorąc, testuje standardową bibliotekę, a nie kompilator, i chociaż jest poprawna w C ++ 03 i poprawna w C ++ 0x, nie jest poprawna w C ++ 98, więc po wprowadzeniu pewnych poprawek można by wykryć C ++ 98 / C ++ 03 / C ++ 0x stdlib
Jonathan