Kann C ++ - Code sowohl in C ++ 03 als auch in C ++ 11 gültig sein, aber verschiedene Dinge tun?

298

Ist es möglich, dass C ++ - Code sowohl dem C ++ 03- Standard als auch dem C ++ 11- Standard entspricht, aber je nachdem, unter welchem ​​Standard er kompiliert wird, unterschiedliche Aktionen ausführt?

Erik Sjölund
quelle
26
Ich bin mir ziemlich sicher, autodass dies zu einer
solchen
8
Ja. Ein Beispiel ist die >>Verwendung in einer Vorlage. Sie können eine Situation entwickeln, in der beide Standards kompiliert werden können. Eine andere, für die ich sicher leicht Änderungen finden würde, ist die Initialisierung.
Chris
5
Hier ist ein schöner Artikel über die >> Situation: gustedt.wordpress.com/2013/12/15/...
chris
6
@OMGtechy: Ich glaube nicht, dass auto dies verursacht werden kann. Mit der alten Bedeutung autoerfordert eine Deklaration einen Typnamen. Mit der neuen Bedeutung ist ein Typname nicht zulässig.
Keith Thompson
2
Wie ist es offen? Sie selbst haben durch eine andere Frage darauf hingewiesen, dass die Antwort auf diese Frage "Ja, hier ist ein Beispiel dafür, wie" lautet. Wie Sie selbst betont haben, gibt es eine sehr eindeutige Antwort auf die Frage.
Jalf

Antworten:

283

Die Antwort ist ein klares Ja. Auf der positiven Seite gibt es:

  • Code, der zuvor Objekte implizit kopiert hat, verschiebt sie jetzt implizit, wenn dies möglich ist.

Auf der negativen Seite sind einige Beispiele im Anhang C der Norm aufgeführt. Obwohl es viel mehr negative als positive gibt, ist es viel weniger wahrscheinlich, dass jeder von ihnen auftritt.

String-Literale

#define u8 "abc"
const char* s = u8"def"; // Previously "abcdef", now "def"

und

#define _x "there"
"hello "_x // Previously "hello there", now a user defined string literal

Geben Sie Konvertierungen von 0 ein

In C ++ 11 sind nur Literale ganzzahlige Nullzeigerkonstanten:

void f(void *); // #1
void f(...); // #2
template<int N> void g() {
    f(0*N); // Calls #2; used to call #1
}

Abgerundete Ergebnisse nach Integer Division und Modulo

In C ++ 03 durfte der Compiler entweder gegen 0 oder gegen negative Unendlichkeit runden. In C ++ 11 muss auf 0 gerundet werden

int i = (-1) / 2; // Might have been -1 in C++03, is now ensured to be 0

Leerzeichen zwischen verschachtelten Klammern zum Verschachteln von Vorlagen >> vs >>

Innerhalb einer Spezialisierung oder Instanziierung >>kann dies stattdessen als Rechtsverschiebung in C ++ 03 interpretiert werden. Es ist jedoch wahrscheinlicher, dass vorhandener Code beschädigt wird: (von http://gustedt.wordpress.com/2013/12/15/a-disimprovement-observed-from-the-outside-right-angle-brackets/ )

template< unsigned len > unsigned int fun(unsigned int x);
typedef unsigned int (*fun_t)(unsigned int);
template< fun_t f > unsigned int fon(unsigned int x);

void total(void) {
    // fon<fun<9> >(1) >> 2 in both standards
    unsigned int A = fon< fun< 9 > >(1) >>(2);
    // fon<fun<4> >(2) in C++03
    // Compile time error in C++11
    unsigned int B = fon< fun< 9 >>(1) > >(2);
}

Der Bediener newkann jetzt andere Ausnahmen als auslösenstd::bad_alloc

struct foo { void *operator new(size_t x){ throw std::exception(); } }
try {
    foo *f = new foo();
} catch (std::bad_alloc &) {
    // c++03 code
} catch (std::exception &) {
    // c++11 code
}

Vom Benutzer deklarierte Destruktoren haben ein implizites Beispiel für eine Ausnahmespezifikation aus Welche wichtigen Änderungen werden in C ++ 11 eingeführt?

struct A {
    ~A() { throw "foo"; } // Calls std::terminate in C++11
};
//...
try { 
    A a; 
} catch(...) { 
    // C++03 will catch the exception
} 

size() von Containern ist jetzt erforderlich, um in O (1) zu laufen

std::list<double> list;
// ...
size_t s = list.size(); // Might be an O(n) operation in C++03

std::ios_base::failureleitet sich nicht std::exceptionmehr direkt von ab

Die direkte Basisklasse ist zwar neu, std::runtime_erroraber nicht. So:

try {
    std::cin >> variable; // exceptions enabled, and error here
} catch(std::runtime_error &) {
    std::cerr << "C++11\n";
} catch(std::ios_base::failure &) {
    std::cerr << "Pre-C++11\n";
}
Beispiel
quelle
11
Schön, +1. Eine andere ist, dass ein Benutzer, der jetzt als Destruktor deklariert wurde, implizit noexecpt(true)so ist, dass throwein Destruktor jetzt aufruft std::terminate. Aber ich hoffe, jeder, der einen solchen Code geschrieben hat, wird sich darüber freuen!
typ1232
4
Aber std :: system_error selbst ist (indirekt) von std :: exception abgeleitet und catch (std::exception &)fängt also immer noch std::ios_base::failure.
user2665887
@ user2665887 du hast recht. es kann immer noch das Verhalten eines Programms beeinflussen, aber ich kann mir momentan kein minimales Beispiel vorstellen.
Beispiel
4
Ich bin super verwirrt, da das, was Sie sagen, operator newgenau ist (es kann jetzt werfen std::bad_array_new_length), aber Ihr Beispiel zeigt das überhaupt nicht. Der angezeigte Code ist in C ++ 03 und C ++ 11 AFAIK identisch.
Mooing Duck
2
Die Kehrseite von list :: size ist O (1), dass der Spleiß jetzt O (n) ist
Tony Delroy
55

Ich verweise Sie auf diesen Artikel und das Follow-up , das ein schönes Beispiel dafür enthält, wie >>die Bedeutung von C ++ 03 in C ++ 11 geändert werden kann, während in beiden noch kompiliert wird.

bool const one = true;
int const two = 2;
int const three = 3;

template<int> struct fun {
    typedef int two;
};

template<class T> struct fon {
    static int const three = ::three;
    static bool const one = ::one;
};

int main(void) {
    fon< fun< 1 >>::three >::two >::one; // valid for both  
}

Der Schlüsselteil ist die Zeile in main, die ein Ausdruck ist.

In C ++ 03:

1 >> ::three = 0
=> fon< fun< 0 >::two >::one;

fun< 0 >::two = int
=> fon< int >::one

fon< int >::one = true
=> true

In C ++ 11

fun< 1 > is a type argument to fon
fon< fun<1> >::three = 3
=> 3 > ::two > ::one

::two is 2 and ::one is 1
=> 3 > 2 > 1
=> (3 > 2) > 1
=> true > 1
=> 1 > 1
=> false

Herzlichen Glückwunsch, zwei unterschiedliche Ergebnisse für den gleichen Ausdruck. Zugegeben, das C ++ 03 hat beim Testen ein Warnformular für Clang erstellt.

chris
quelle
es ist seltsam , dass es nicht erforderlich ist typenamefür ::twoin C ++ 03 - Version
zahir
3
Schön, es zum Kochen zu bringen, um nach trueoder falsefür die verschiedenen Standards zu bewerten . Vielleicht könnten wir es als Funktionstest verwenden </ witz>
cmaster - Monica
@zahir, Es ist kein Typ, nur ein Wert.
Chris
Nun, richtige cmdline-Optionen warnen davor ( warning: comparisons like ‘X<=Y<=Z’ do not have their mathematical meaning [-Wparentheses]), aber immer noch ein schönes Beispiel dafür, wie der mehrdeutige ::Operator seine Bedeutung ändert (entweder in Bezug auf den globalen Bereich oder indem er den direkt davor stehenden Operator dereferenziert)
Beispiel
@Beispiel, Überraschenderweise gibt GCC diese Warnung, Clang jedoch nicht.
Chris
39

Ja, es gibt eine Reihe von Änderungen, die dazu führen, dass derselbe Code zu einem unterschiedlichen Verhalten zwischen C ++ 03 und C ++ 11 führt. Die Unterschiede bei den Sequenzierungsregeln führen zu einigen interessanten Änderungen, einschließlich eines zuvor nicht definierten Verhaltens, das genau definiert wird.

1. Mehrere Mutationen derselben Variablen innerhalb einer Initialisiererliste

Ein sehr interessanter Eckfall wären mehrere Mutationen derselben Variablen innerhalb einer Initialisiererliste, zum Beispiel:

int main()
{
    int count = 0 ;
    int arrInt[2] = { count++, count++ } ;

    return 0 ;
}

Sowohl in C ++ 03 als auch in C ++ 11 ist dies gut definiert, aber die Reihenfolge der Auswertung in C ++ 03 ist nicht spezifiziert, aber in C ++ 11 werden sie in der Reihenfolge ausgewertet, in der sie erscheinen . Wenn wir also clangim C ++ 03-Modus kompilieren , wird die folgende Warnung ausgegeben ( siehe live ):

warning: multiple unsequenced modifications to 'count' [-Wunsequenced]

    int arrInt[2] = { count++, count++ } ;

                           ^        ~~

In C ++ 11 wird jedoch keine Warnung angezeigt ( siehe live ).

2. Neue Sequenzierungsregeln machen i = ++ i + 1; gut definiert in C ++ 11

Die neuen Sequenzierungsregeln, die nach C ++ 03 übernommen wurden, bedeuten:

int i = 0 ;
i = ++ i + 1;

ist in C ++ 11 kein undefiniertes Verhalten mehr, dies wird im Fehlerbericht 637 behandelt. Sequenzierungsregeln und Beispiel stimmen nicht überein

3. Neue Sequenzierungsregeln machen auch ++++ i; gut definiert in C ++ 11

Die neuen Sequenzierungsregeln, die nach C ++ 03 übernommen wurden, bedeuten:

int i = 0 ;
++++i ;

ist in C ++ 11 kein undefiniertes Verhalten mehr.

4. Etwas vernünftiger signierte Linksverschiebungen

Spätere Entwürfe von C ++ 11, N3485die ich unten verlinke, haben das undefinierte Verhalten beim Verschieben eines 1-Bits in das Vorzeichenbit oder darüber hinaus behoben . Dies wird auch im Fehlerbericht 1457 behandelt . Howard Hinnant kommentierte die Bedeutung dieser Änderung im Thread auf Ist Linksverschiebung (<<) ein negatives ganzzahliges undefiniertes Verhalten in C ++ 11? .

5. constexpr-Funktionen können in C ++ 11 als Ausdrücke für die Kompilierungszeitkonstante behandelt werden

C ++ 11 führte constexpr- Funktionen ein, die:

Der constexpr-Bezeichner erklärt, dass es möglich ist, den Wert der Funktion oder Variablen zur Kompilierungszeit auszuwerten. Solche Variablen und Funktionen können dann verwendet werden, wenn nur Ausdrücke für die Kompilierungszeitkonstante zulässig sind.

Während C ++ 03 nicht über die Funktion constexpr verfügt, müssen wir das Schlüsselwort constexpr nicht explizit verwenden, da die Standardbibliothek in C ++ 11 viele Funktionen als constexpr bereitstellt . Zum Beispiel std :: numeric_limits :: min . Was zu unterschiedlichem Verhalten führen kann, zum Beispiel:

#include <limits>

int main()
{
    int x[std::numeric_limits<unsigned int>::min()+2] ;
}

Bei Verwendung clangin C ++ 03 handelt es xsich um ein Array mit variabler Länge, das eine Erweiterung darstellt und die folgende Warnung generiert:

warning: variable length arrays are a C99 feature [-Wvla-extension]
    int x[std::numeric_limits<unsigned int>::min()+2] ;
         ^

In C ++ 11 std::numeric_limits<unsigned int>::min()+2handelt es sich um einen Ausdruck für die Kompilierungszeitkonstante, für den die VLA-Erweiterung nicht erforderlich ist.

6. In C ++ 11 werden implizit keine Ausnahmespezifikationen für Ihre Destruktoren generiert

Da in C ++ 11 der benutzerdefinierte Destruktor eine implizite noexcept(true)Spezifikation hat, wie in noexcept destructors erläutert , bedeutet dies, dass das folgende Programm:

#include <iostream>
#include <stdexcept>

struct S
{
  ~S() { throw std::runtime_error(""); } // bad, but acceptable
};

int main()
{
  try { S s; }
  catch (...) {
    std::cerr << "exception occurred";
  } 
 std::cout << "success";
}

In C ++ 11 wird aufgerufen, std::terminateaber in C ++ 03 erfolgreich ausgeführt.

7. In C ++ 03 konnten Vorlagenargumente keine interne Verknüpfung haben

Dies wird ausführlich in Warum std :: sort keine in einer Funktion deklarierten Vergleichsklassen behandelt . Der folgende Code sollte also in C ++ 03 nicht funktionieren:

#include <iostream>
#include <vector>
#include <algorithm>

class Comparators
{
public:
    bool operator()(int first, int second)
    {
        return first < second;
    }
};

int main()
{
    class ComparatorsInner : public Comparators{};

    std::vector<int> compares ;
    compares.push_back(20) ;
    compares.push_back(10) ;
    compares.push_back(30) ;

    ComparatorsInner comparatorInner;
    std::sort(compares.begin(), compares.end(), comparatorInner);

    std::vector<int>::iterator it;
    for(it = compares.begin(); it != compares.end(); ++it)
    {
        std::cout << (*it) << std::endl;
    }
}

Derzeit clangist dieser Code jedoch im C ++ 03-Modus mit einer Warnung -pedantic-errorszulässig, es sei denn, Sie verwenden ein Flag, das irgendwie icky ist. Sehen Sie ihn live .

8. >> ist beim Schließen mehrerer Vorlagen nicht mehr schlecht geformt

Die Verwendung >>zum Schließen mehrerer Vorlagen ist nicht mehr fehlerhaft, kann jedoch zu Code mit unterschiedlichen Ergebnissen in C ++ 03 und C + 11 führen. Das folgende Beispiel stammt aus rechtwinkligen Klammern und der Abwärtskompatibilität :

#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';
  std::cout << (Y<X< 1>>::c >::c>::c) << '\n';
}

und das Ergebnis in C ++ 03 ist:

0
3

und in C ++ 11:

0
0

9. C ++ 11 ändert einige der std :: vector-Konstruktoren

Leicht modifizierter Code aus dieser Antwort zeigt, dass der folgende Konstruktor von std :: vector verwendet wird :

std::vector<T> test(1);

erzeugt unterschiedliche Ergebnisse in C ++ 03 und C ++ 11:

#include <iostream>
#include <vector>

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


int main()
{
    std::vector<T> test(1);
    bool is_cpp11 = !test[0].flag;

    std::cout << is_cpp11 << std::endl ;
}

10. Eingrenzen der Konvertierungen in aggregierten Initialisierern

In C ++ 11 ist eine sich verengende Konvertierung in Aggregatinitialisierern fehlerhaft und es sieht so aus, als ob gccdies sowohl in C ++ 11 als auch in C ++ 03 möglich ist, obwohl in C ++ 11 standardmäßig eine Warnung angezeigt wird:

int x[] = { 2.0 };

Dies wird im Entwurf des C ++ 11-Standardabschnitts, 8.5.4 Listeninitialisierung, Absatz 3, behandelt :

Die Listeninitialisierung eines Objekts oder einer Referenz vom Typ T ist wie folgt definiert:

und enthält die folgende Kugel ( Schwerpunkt Mine ):

Andernfalls werden Konstruktoren berücksichtigt, wenn T ein Klassentyp ist. Die anwendbaren Konstruktoren werden aufgelistet und der beste wird durch Überlastungsauflösung ausgewählt (13.3, 13.3.1.7). Wenn eine einschränkende Konvertierung (siehe unten) erforderlich ist, um eines der Argumente zu konvertieren, ist das Programm fehlerhaft

Dieses und viele mehr Instanz in der überdachten Entwurf C ++ Standard Abschnitt annex C.2 C ++ und ISO C ++ 2003 . Es enthält auch:

  • Neue Arten von Zeichenfolgenliteralen [...] Insbesondere Makros mit den Namen R, u8, u8R, u, uR, U, UR oder LR werden nicht erweitert, wenn sie an ein Zeichenfolgenliteral angrenzen, sondern als Teil des Zeichenfolgenliteral interpretiert . Beispielsweise

    #define u8 "abc"
    const char *s = u8"def"; // Previously "abcdef", now "def"
  • Benutzerdefinierte Unterstützung für Literalzeichenfolgen [...] Bisher bestand # 1 aus zwei separaten Vorverarbeitungstoken, und das Makro _x wurde erweitert. In diesem internationalen Standard besteht # 1 aus einem einzelnen Vorverarbeitungstoken, sodass das Makro nicht erweitert wird.

    #define _x "there"
    "hello"_x // #1
  • Geben Sie eine Rundung für die Ergebnisse des Integer / und% [...] 2003-Codes an, der eine Ganzzahldivision verwendet, um das Ergebnis gegen 0 oder gegen negative Unendlichkeit zu runden, während dieser Internationale Standard das Ergebnis immer gegen 0 rundet.

  • Die Komplexität der Mitgliedsfunktionen von size () ist jetzt konstant. [...] Einige Containerimplementierungen, die C ++ 2003 entsprechen, entsprechen möglicherweise nicht den in dieser internationalen Norm angegebenen Anforderungen an size (). Das Anpassen von Containern wie std :: list an die strengeren Anforderungen erfordert möglicherweise inkompatible Änderungen.

  • Die Basisklasse von std :: ios_base :: fail ändern [...] std :: ios_base :: fail wird nicht mehr direkt von std :: exception abgeleitet, sondern wird jetzt von std :: system_error abgeleitet, von dem wiederum abgeleitet wird std :: runtime_error. Gültiger C ++ 2003-Code, der davon ausgeht, dass std :: ios_base :: fail direkt von std :: exception abgeleitet ist, wird in diesem internationalen Standard möglicherweise anders ausgeführt.

Shafik Yaghmour
quelle
Die meisten Beispiele beschränken sich also auf die Tatsache, dass bisher undefiniertes Verhalten jetzt gut definiert ist?
MatthiasB
@MatthiasB 2, 3 und 4 befassen sich damit, daher sind sie zu diesem Zeitpunkt nicht mehr die Mehrheit der Beispiele. Ich bezweifle, dass ich viel mehr undefinierte Verhaltensbeispiele finden werde, wenn ich mehr hinzufüge, werden sie zu einer kleineren Menge.
Shafik Yaghmour
Nun, # 1 Verhalten ist nicht spezifiziert, also würde ich es als undefiniertes Verhalten zählen (zumindest können Sie nicht erwarten, ein bestimmtes Ergebnis mit C ++ 03 zu erhalten, jetzt mit C ++ 11 können Sie), # 5 verwendet ein Nicht-Verhalten Standarderweiterung von c ++. Aber ich denke du hast recht. Je mehr Sie danach suchen, desto mehr Beispiele finden Sie, die in beiden Standards definiert sind, aber unterschiedliche Ergebnisse liefern.
MatthiasB
@MatthiasB Ja, sowohl nicht spezifiziertes als auch undefiniertes Verhalten haben unerwünschte Ergebnisse. Bei Erweiterungen, die Linux berücksichtigen, hängt dies von einer Reihe von gcc-Erweiterungen ab, die wir in der realen Welt annehmen sollten. Ich hatte nicht erwartet, so viele Beispiele zu finden, als ich diese Frage zum ersten Mal beantwortete.
Shafik Yaghmour
35

Eine potenziell gefährliche rückwärts inkompatible Änderung betrifft Konstruktoren von Sequenzcontainern, z. B. std::vectordie Überladung, die die Anfangsgröße angibt. Während in C ++ 03 ein standardmäßig erstelltes Element kopiert wurde, wird in C ++ 11 jedes standardmäßig erstellt.

Betrachten Sie dieses Beispiel (mit boost::shared_ptrC ++ 03):

#include <deque>
#include <iostream>

#include "boost/shared_ptr.hpp"


struct Widget
{
  boost::shared_ptr<int> p;

  Widget() : p(new int(42)) {}
};


int main()
{
  std::deque<Widget> d(10);
  for (size_t i = 0; i < d.size(); ++i)
    std::cout << "d[" << i << "] : " << d[i].p.use_count() << '\n';
}

C ++ 03 Live-Beispiel

C ++ 11 Live-Beispiel

Der Grund dafür ist, dass C ++ 03 eine Überladung für "Größe und Prototypelement angeben" und "Nur Größe angeben" wie folgt angegeben hat (Allokatorargumente der Kürze halber weggelassen):

container(size_type size, const value_type &prototype = value_type());

Dies wird immer prototypein die Containerzeiten kopiert size. Wenn es mit nur einem Argument aufgerufen wird, werden daher sizeKopien eines standardmäßig erstellten Elements erstellt.

In C ++ 11 wurde diese Konstruktorsignatur entfernt und durch diese beiden Überladungen ersetzt:

container(size_type size);

container(size_type size, const value_type &prototype);

Der zweite funktioniert wie zuvor und erstellt sizeKopien des prototypeElements. Der erste (der jetzt Aufrufe nur mit dem angegebenen Größenargument verarbeitet) erstellt jedoch standardmäßig jedes Element einzeln.

Meine Vermutung für den Grund dieser Änderung ist, dass die C ++ 03-Überladung mit einem Elementtyp, der nur verschoben werden kann, nicht verwendet werden kann. Aber es ist trotzdem eine bahnbrechende Veränderung, und eine, die selten dokumentiert wird.

Angew ist nicht mehr stolz auf SO
quelle
3
Obwohl dies offensichtlich eine bahnbrechende Änderung ist, bevorzuge ich das C ++ 11-Verhalten. Ich würde erwarten, dass dies dazu führt deque, dass zehn separate Widgets gespeichert werden und nicht zehn Widgets, die dieselbe Ressource gemeinsam nutzen.
Agentlien
19

Das Ergebnis eines fehlgeschlagenen Lesevorgangs von std::istreamhat sich geändert. CppReference fasst es gut zusammen:

Wenn die Extraktion fehlschlägt (z. B. wenn ein Buchstabe eingegeben wurde, bei dem eine Ziffer erwartet wird), valuebleibt er unverändert und failbitwird gesetzt. (bis C ++ 11)

Wenn die Extraktion fehlschlägt, wird Null geschrieben valueund failbitgesetzt. Wenn die Extraktion dazu führt, dass der Wert zu groß oder zu klein ist, um hinein zu passen value, std::numeric_limits<T>::max()oder std::numeric_limits<T>::min()geschrieben wird und das failbitFlag gesetzt ist. (seit C ++ 11)

Dies ist in erster Linie ein Problem, wenn Sie an die neue Semantik gewöhnt sind und dann mit C ++ 03 schreiben müssen. Folgendes ist keine besonders gute Praxis, aber in C ++ 11 gut definiert:

int x, y;
std::cin >> x >> y;
std::cout << x + y;

In C ++ 03 verwendet der obige Code jedoch eine nicht initialisierte Variable und weist daher ein undefiniertes Verhalten auf.

Anton Golov
quelle
4
Sie können hinzufügen, dass in C ++ 03 dieses standardisierte Verhalten verwendet werden könnte , um einen Standardwert wie in bereitzustellen int x = 1, y = 1; cin >> x >> y; cout << x*y;. Mit C ++ 03 hätte dies korrekt erzeugt, xwenn kein ygelesen werden konnte.
cmaster - wieder herstellen Monica
15

Dieser Thread Welche Unterschiede zwischen C ++ 03 und C ++ 0x, falls vorhanden, zur Laufzeit erkannt werden können, enthält Beispiele (von diesem Thread kopiert), um Sprachunterschiede zu ermitteln, z. B. durch Ausnutzen der reduzierten C ++ 11-Referenz:

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

bool isCpp11() 
{
    int v = 1;
    return f<int&>(v); 
}

und c ++ 11, das lokale Typen als Vorlagenparameter zulässt:

template <class T> bool cpp11(T)  {return true;} //T cannot be a local type in C++03
                   bool cpp11(...){return false;}

bool isCpp0x() 
{
   struct local {} var; //variable with local type
   return cpp11(var);
}
uwedolinsky
quelle
7

Hier ist ein weiteres Beispiel:

#include <iostream>

template<class T>
struct has {
  typedef char yes;
  typedef yes (&no)[2];    
  template<int> struct foo;    
  template<class U> static yes test(foo<U::bar>*);      
  template<class U> static no  test(...);    
  static bool const value = sizeof(test<T>(0)) == sizeof(yes);
};

enum foo { bar };

int main()
{
    std::cout << (has<foo>::value ? "yes" : "no") << std::endl;
}

Drucke:

Using c++03: no
Using c++11: yes

Sehen Sie das Ergebnis auf Coliru

StackedCrooked
quelle