2015-06-02 5 views
19

Mir ist sehr wohl bewusst, dass die Übergabe eines als Template-Nicht-Typparameter fehlerhaft ist, da zwei identische Stringliterale in zwei verschiedenen Übersetzungseinheiten unterschiedliche Adressen haben können (obwohl die Compiler meistens die selbe Adresse benutzen). Es gibt einen Trick man verwenden kann, siehe nachfolgender Code:Template-Tricks mit const char * als Nicht-Typparameter

#include <iostream> 

template<const char* msg> 
void display() 
{ 
    std::cout << msg << std::endl; 
} 

// need to have external linkage 
// so that there are no multiple definitions 
extern const char str1[] = "Test 1"; // (1) 

// Why constexpr is enough? Does it have external linkage? 
constexpr char str2[] = "Test 2"; // (2) 

// Why doesn't this work? 
extern const char* str3 = "Test 3"; // (3) doesn't work 

// using C_PTR_CHAR = const char* const; // (4) doesn't work either 
extern constexpr C_PTR_CHAR str4 = "Test 4"; 

int main() 
{ 
    display<str1>(); // (1') 
    display<str2>(); // (2') 
    // display<str3>(); // (3') doesn't compile 
    //display<str4>(); // (4') doesn't compile 
} 

Grundsätzlich in (1) Wir erklären und definiert einen Array mit externer Bindung, die dann als Template-Parameter verwendet werden kann, in (1') . Ich verstehe das sehr gut. Allerdings verstehe ich nicht:

  1. Warum die constexpr Version (2) funktioniert? Haben constexpr eine externe Verbindung? Wenn nicht, dann kann die Definition des gleichen String-Literals in einer anderen Übersetzungseinheit zu einer doppelten Template-Instanziierung führen.

  2. Warum (3) und (4) funktionieren nicht? Es scheint durchaus sinnvoll für mich, aber der Compiler nicht glaubt, so:

    error: 'str3' is not a valid template argument because 'str3' is a variable, not the address of a variable

+0

Ich habe vor kurzem eine ähnliche Frage, die gefragt, warum eine 'extern' -Deklaration nicht für die Kompilierung Zeit Wert Abzug verwendet werden kann. Es ist wahrscheinlich, weil der Linker tatsächlich "extern" behandelt, und der Compiler kann diese Information nicht verwenden, wenn der Code kompiliert wird. –

+0

@ πάνταῥεῖ Was seltsam ist, ist, dass ich in (1) eine 'extern'-Definition verwende, die funktioniert. – vsoftco

+0

Was '(3)' von den anderen beiden unterscheidet, ist, dass Sie möchten, dass die Adresse "innerhalb" der Variablen enthalten ist und nicht die Adresse * von * der Variablen selbst. Ich bin gerade an meinem Telefon und kann diese Frage zu einem späteren Zeitpunkt noch einmal beantworten, um eine Antwort zu geben. –

Antwort

12

1. Kurze Antwort: Es funktioniert unabhängig davon constexpr erklärt zu werden, weil Sie ein Objekt sind definiert, mit Statische Speicherdauer (das ist kein String-Literal - es speichert eine Kopie des Inhalts von eins) und seine Adresse ist ein konstanter Ausdruck. In Bezug auf die Verknüpfung hat str2 interne Verknüpfung, aber das ist in Ordnung - seine Adresse kann als ein nicht-Vorlage-Argument verwendet werden.

Lange Antwort:

In C++ 11 und 14 [14.3.2p1] sagt der folgende:

A template-argument for a non-type, non-template template-parameter shall be one of:
[...]

  • a constant expression (5.19) that designates the address of a complete object with static storage duration and external or internal linkage or a function with external or internal linkage, including function templates and function template-ids but excluding non-static class members, expressed (ignoring parentheses) as &id-expression, where the id-expression is the name of an object or function, except that the & may be omitted if the name refers to a function or array and shall be omitted if the corresponding template-parameter is a reference;

[...]

So können Sie die Adresse eines Objekts mit statischer Speicherdauer verwenden, aber das Objekt muss durch einen Namen mit einer Verknüpfung (intern oder extern) identifiziert werden, und die Art, wie Sie diese Adresse ausdrücken, ist eingeschränkt. (Zeichenfolgenliterale sind keine Namen und haben keine Verknüpfung.)

Kurz gesagt, funktioniert sogar char str1[] = "Test 1";. static char str1[] = "Test 1"; ist auch in Ordnung; GCC 5.1.0 lehnt dies ab, aber ich denke, das ist ein Bug; Clang 3.6.0 akzeptiert es.


Über str2 ‚s-Bindung, C++ 11 und 14 [3.5p3] sagt:

A name having namespace scope (3.3.6) has internal linkage if it is the name of
[...]

  • a non-volatile variable that is explicitly declared const or constexpr and neither explicitly declared extern nor previously declared to have external linkage;

[...]

N4431 hat, dass etwas geändert, als Folge der DR 1686, zu:

  • a variable of non-volatile const-qualified type that is neither explicitly declared extern nor previously declared to have external linkage;

widerspiegelt die Tatsache, dass constexpr const-Qualifikation für Objekte bedeutet.


2. Kurze Antwort: Für C++ 11 und 14, siehe oben; für Entwurf C++ 1z, str3 ist kein konstanter Ausdruck, da der Zeiger selbst nicht constexpr ist, und es ist auch die Adresse eines String-Literals. str4 ist konstant, aber immer noch eine Adresse eines String-Literals.

Lange Antwort:

Im aktuellen Arbeitsentwurf, N4431, die Beschränkungen für Nicht-Typ-Vorlage Argumente wurden gelockert. [14.3.2p1] sagt jetzt:

A template-argument for a non-type template-parameter shall be a converted constant expression (5.20) of the type of the template-parameter. For a non-type template-parameter of reference or pointer type, the value of the constant expression shall not refer to (or for a pointer type, shall not be the address of):

  • a subobject (1.8),
  • a temporary object (12.2),
  • a string literal (2.13.5),
  • the result of a typeid expression (5.2.8), or
  • a predefined __func__ variable (8.4.1).

Und das sind alle Einschränkungen. Der konvertierte konstante Ausdruck Teil ist ziemlich wichtig; Die vollständige Definition ist lang, aber ein Teil relevant für unseren Fall ist, dass die Adresse eines Objekts mit statischer Speicherdauer ein solcher Ausdruck ist.

Von Bedeutung ist auch, dass nach [5.20p2.7], ein L-Wert-zu-R-Wert-Umwandlungs angewendet

a non-volatile glvalue that refers to a non-volatile object defined with constexpr , or that refers to a non-mutable sub-object of such an object

auch die Voraussetzungen für ein konstanter Ausdruck ist. Dies ermöglicht uns, einige constexpr Zeigervariablen als nicht typisierte Vorlagenargumente zu verwenden. (Beachten Sie, dass die einfache Deklaration einer Variablen const nicht ausreicht, da sie mit einem nicht konstanten Ausdruck initialisiert werden kann.)

So etwas wie constexpr const char* str3 = str1; ist in Ordnung. Es wird von Clang 3.6.0 im C++ 1z-Modus akzeptiert (und im C++ 14-Modus abgelehnt); GCC 5.1.0 lehnt es immer noch ab - es sieht so aus, als hätte es die aktualisierten Regeln noch nicht implementiert.


Dennoch, was ist falsch mit String-Literalen? Hier ist das Problem (N4431 [2.13.5p16]):

Evaluating a string-literal results in a string literal object with static storage duration, initialized from the given characters as specified above. Whether all string literals are distinct (that is, are stored in nonoverlapping objects) and whether successive evaluations of a string-literal yield the same or a different object is unspecified.

Eine Implementierung erlaubt ist, viele Dinge mit Stringliterale zu tun: mix, match, machen sie sich überlappen (ganz oder teilweise), machen 7 Kopien von der gleiche Übersetzungseinheit - was auch immer. Das macht die Adresse eines Zeichenfolgenliterals als ein nicht typisiertes Vorlagenargument unbrauchbar.

+1

Ich kann sogar den Zeiger "const" und "constexpr" machen, siehe die leicht aktualisierte Bearbeitung, immer noch das gleiche Problem. Die Tatsache, dass es sich um die Adresse eines String-Literals handelt, sollte kein Problem sein, vorausgesetzt, es hat eine externe Verknüpfung, so dass die Adresse für alle Übersetzungseinheiten gleich ist. Aber der Standard sagt nein, ich sehe ... Ich denke, meine Frage ist eher wie * "Warum sagt der Standard Nein?" * Danke für das Zitat trotzdem! – vsoftco

+0

Also funktionieren die ersten beiden Beispiele, weil sie technisch Zeichenfelder sind, die aus Zeichenfolgenliteralen und nicht aus Zeichenfolgenliteralen selbst bestehen? – jaggedSpire

+0

@jaggedSpire Ja, das ist definitiv der Fall. – bogdan