3 Stimmen

Die Reihenfolge der Deklaration von Funktionsschablonen beeinflusst die Sichtbarkeit (manchmal)

Ich versuche, eine Funktion zu erstellen:

template <typename T>
void doIt( T*& p )
{
   if ( !p ) { return; }
   T& ref = *p;
   getClassName( ref );
}

wobei sich das Verhalten je nach Art der p übergeben. Insbesondere die Version von getClassName sollte sich nach der Art der p . Im folgenden Beispiel kann ich erfolgreich aufrufen:

doIt<myClass1>( myClass1*& )
doIt<myClass1<int> >( myClass1*& )
doIt<myClass2>( myClass2*& )
doIt<myClass2<int> >( myClass2*& )

aber es schlägt fehl, wenn ich anrufe:

doIt< std::vector<int, std::allocator<int> > >( std::vector<int, std::allocator<int>>*& )

mit dem Fehler:

a.cxx: In function ‘void doIt(T*&) [with T = std::vector<int, std::allocator<int> >]’:
ba.cxx:87:   instantiated from here
a.cxx:33: error: invalid initialization of reference of type ‘MyClass1&’ from expression of type ‘std::vector<int, std::allocator<int> >’
a.cxx:16: error: in passing argument 1 of ‘const char* getClassName(MyClass1&)’

(gcc 4.2.4).

Wenn ich die Erklärung von verschiebe:

template<typename T, typename A>
char const* getClassName( std::vector<T,A>& ) { printf("std::vector<T,A>\n"); return NULL; }

vor doIt - dann wird es kompiliert. So,

  • Warum ist es erforderlich, dass getClassName( std::vector<T,A>& ) erscheint vor doIt aber nicht getClassName( MyClass2T<T>& )
  • Was kann ich tun, damit doIt unabhängig von std::vector ? (Ich möchte in der Lage sein, zu platzieren doIt in seiner eigenen Kopfzeile und muss nicht wissen, dass std::vector oder eine der Spezialisierungen, die benutzerdefiniert sein werden).

.

#include <stdio.h>
#include <assert.h>
#include <vector>

//template<typename T>
//char const* getClassName( T& );

//template<typename T, typename A>
////char const* getClassName( std::vector<T,A>& ) { printf("std::vector<T,A>\n"); return NULL; }

#if 1
// ---------  MyClass2
struct MyClass1
{};

char const* getClassName( MyClass1& ) { printf("MyClass1\n"); return NULL; }

// ---------  MyClass1T
template< typename T>
struct MyClass1T
{};

template<typename T>
char const* getClassName( MyClass1T<T>& ) { printf("MyClass1T<T>\n"); return NULL; }
#endif

template <typename T>
void doIt( T*& p )
{
   if ( !p ) { return; }
   T& ref = *p;
   getClassName( ref );
}

// ---------  MyClass2
struct MyClass2
{};

// declared after doIt, OK.
char const* getClassName( MyClass2& ) { printf("MyClass2\n"); return NULL; }

// ---------  MyClass2T
template< typename T>
struct MyClass2T
{};

// declared after doIt, OK.
template<typename T>
char const* getClassName( MyClass2T<T>& ) { printf("MyClass2T<T>\n"); return NULL; }

template<typename T, typename A>
char const* getClassName( std::vector<T,A>& ) { printf("std::vector<T,A>\n"); return NULL; }

void test()
{
#if 1
   MyClass1 mc1;
   MyClass1* mc1p = &mc1;
   doIt( mc1p );

   MyClass2 mc2;
   MyClass2* mc2p = &mc2;
   doIt( mc2p );

   MyClass1T<int> mc1t;
   MyClass1T<int>* mc1tp = &mc1t;
   doIt( mc1tp );

   MyClass2T<int> mc2t;
   MyClass2T<int>* mc2tp = &mc2t;
   doIt( mc2tp );

   // Nested templates are OK.
   MyClass2T<MyClass1> mc2t2;
   MyClass2T<MyClass1>* mc2tp2 = &mc2t2;
   doIt( mc2tp2 );
#endif

#if 1
   std::vector<int> v;
   std::vector<int>* vp = &v;
   doIt( vp );                   // FAIL!
#endif
}

4voto

dirkgently Punkte 104289

Warum ist es erforderlich, dass getClassName( std::vector& ) vor doIt erscheint, aber nicht getClassName( MyClass2T& )

Eine Deklaration in scope ist für jede Funktion erforderlich. Wenn Sie Ihre Vorlagenfunktion mit einer vector<int> erwartet er eine Funktion mit der Signatur getClassName(vector<int>&) vorhanden sein muss (zumindest ein Prototyp), damit die Kompilierung erfolgreich ist.

Was kann ich tun, um doIt unabhängig von std::vector zu machen? (Ich möchte in der Lage sein, doIt in seinem eigenen Header zu platzieren und muss nichts über std::vector oder eine der Spezialisierungen wissen, die benutzerdefiniert sein werden)

Lesen Sie die FAQ zu Vorlagen . Versuchen Sie, den Prototyp von allen doIt abhängigen Vorlagenfunktionen vor der ersten Instanziierung von doIt .

4voto

Der Grund für den Fehler ist, dass bei der Instanziierung keine unqualifizierte Namenssuche für die Funktionen stattfindet (sondern nur ADL - Argument Dependent Lookup). Der Instanziierungskontext ist (übernommen aus 14.6.4.1/6 des C++-Standards):

Der Instanziierungskontext eines Ausdrucks, der von den Schablonenargumenten abhängt, ist die Menge der Deklarationen mit externer Verknüpfung, die vor dem Zeitpunkt der Instanziierung der Schablonenspezialisierung in derselben Übersetzungseinheit deklariert wurden.

Der Punkt der Instanziierung all dieser Schablonenspezialisierungen, die Sie in diesem Fall aufgerufen haben, liegt direkt nach der Definition von test (lesen 14.6.4.1/1 ). Alle Funktionen, die Sie deklariert haben, sind also in Ihrem test Funktion mit unqualifiziertem lookup, aber lookup für sie ist tatsächlich anders für die Funktionsaufrufe:

Ein Funktionsaufruf, der von einem Template-Parameter innerhalb eines Templates abhängt, wird wie folgt nachgeschlagen:

  • Namen aus dem Kontext der Schablonendefinition werden sowohl von der normalen Suche als auch von der ADL berücksichtigt.
  • Namen aus dem Instanziierungskontext werden nur für ADL berücksichtigt.

Das heißt, weil es keine geeigneten getClassName Funktion, die im Definitionskontext der Vorlage deklariert ist, muss im Instanziierungskontext mittels ADL eine passende Funktion gefunden werden - sonst schlägt der Aufruf fehl und findet keine Deklaration.

Argumentabhängiges Nachschlagen (ADL)

Für ein Argument des Typs std::vector<T> ADL sucht nach Funktionen im Namespace std und der Namespace von T . Das Einsetzen der getClassName Funktion in die std Namespace funktionieren würde (dies ist jedoch nach dem Standard nicht zulässig, da dies zu undefiniertem Verhalten führt - dies sollte nur als letzter Ausweg getan werden).

Um die Auswirkungen von ADL versuchen zu telefonieren doIt mit einem Vektor von MyClass2 anstelle von int . Seitdem T = MyClass2 sucht ADL im Namespace von MyClass2 für eine geeignete Funktion, die eine std::vector<MyClass2> und wird erfolgreich sein - im Gegensatz zur Verwendung von int die sich nur mit folgenden Themen befassen wird std .

Für die anderen Funktionsaufrufe finden sich die entsprechenden Deklarationen ebenfalls, da sie alle im globalen Namensraum deklariert sind, in dem auch die Argumenttypen der Funktionsaufrufe definiert sind ( MyClass1 , MyClass2 usw.).

Die C++-FAQ ist gut, aber sie geht nicht tief in Templates ein (ich habe keine Erwähnung von ADL darin gefunden). Es gibt eine spezielle Vorlagen-FAQ das einige der komplizierteren Fallstricke behandelt.


Vorsicht vor undefiniertem Verhalten

Beachten Sie, dass viele Compiler den Code auch dann akzeptieren, wenn Sie die von mir gezeigte Erklärung nach die test Funktion (statt vor der Funktion). Aber wie das obige Standard-Zitat sagt, wird die Deklaration nicht Teil des Instanziierungskontextes sein und die Regel, die in 14.6.4.2/1 ist zu beobachten:

Wenn der Aufruf nicht formgerecht wäre oder eine bessere Übereinstimmung gefunden hätte, wenn die Suche innerhalb der zugehörigen Namensräume alle Funktionsdeklarationen mit externer Verknüpfung berücksichtigt hätte, die in diesen Namensräumen in allen Übersetzungseinheiten eingeführt wurden, und nicht nur die Deklarationen, die in den Kontexten der Schablonendefinition und Schabloneninstanziierung gefunden wurden, dann hat das Programm ein undefiniertes Verhalten.

Was also zu funktionieren scheint, ist ein undefiniertes Verhalten. Es ist für einen Compiler zulässig, es zu akzeptieren, aber es ist ebenso zulässig, es abzulehnen oder abzustürzen und zu beenden. Achten Sie also darauf, dass jeder benötigte Name tatsächlich im Instanziierungskontext wie erläutert sichtbar ist.

Ich hoffe, das hilft.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X