2015-06-02 12 views
19

Sono perfettamente consapevole del fatto che passare direttamente un const char* come parametro di tipo non di modello è errato, poiché due letterali di stringa identici definiti in due diverse unità di traduzione possono avere indirizzi diversi (anche se la maggior parte delle volte i compilatori usano lo stesso indirizzo). C'è un trucco si può usare, vedi codice qui sottoTrucchi di modello con const char * come parametro non di tipo

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

Fondamentalmente in (1) si dichiara e definire un array con collegamento esterno, che può quindi essere utilizzato come parametro di template in (1') . Lo capisco molto bene. Tuttavia, non capisco:

  1. Perché la versione constexpr (2) funziona? constexpr ha un collegamento esterno? In caso contrario, la definizione della stessa stringa letterale in una diversa unità di traduzione può condurre con un'istanza di modello duplicata.

  2. Perché (3) e (4) non funzionano? Sembra perfettamente ragionevole per me, ma il compilatore non crede così:

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

+0

Ho visto una domanda simile recentemente, che mi ha chiesto perché una dichiarazione "extern" non può essere utilizzata per la deduzione del valore temporale compilato. Probabilmente è perché il linker gestisce effettivamente 'extern', e il compilatore non può usare quelle informazioni quando il codice è compilato. –

+0

@ πάνταῥεῖ Ciò che è strano è che in (1) io uso una definizione di 'extern', che funziona. – vsoftco

+0

ciò che rende '(3)' diverso dagli altri due è che ti piacerebbe che l'indirizzo contenesse "dentro" la variabile, e non l'indirizzo * della * stessa variabile. Sono attualmente sul mio telefono e in tal modo potrei tornare a questa domanda in un secondo momento per fornire una risposta. –

risposta

12

1. Risposta breve: Funziona a prescindere che sia dichiarato constexpr, perché si sta definendo un oggetto con durata dell'archiviazione statica (che non è una stringa letterale - memorizza una copia dei contenuti di uno) e il suo indirizzo è un'espressione costante. Per quanto riguarda il collegamento, str2 ha un collegamento interno, ma va bene - il suo indirizzo può essere utilizzato come argomento modello non di tipo.

Risposta lunga:

In C++ 11 e 14, [14.3.2p1] dice quanto segue:

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;

[...]

Quindi, è possibile utilizzare l'indirizzo di un oggetto con la durata di stoccaggio statico, ma l'oggetto deve essere identificato da un nome con linkage (interno o esterno) e il modo in cui si sta esprimendo tale indirizzo è limitato. (I valori letterali delle stringhe non sono nomi e non hanno linkage.)

In breve, anche char str1[] = "Test 1"; funziona. static char str1[] = "Test 1"; va bene anche; GCC 5.1.0 lo rifiuta, ma penso che sia un bug; Clang 3.6.0 lo accetta.


A proposito di linkage 's str2, C++ 11 e 14 [3.5p3] dice:

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 è cambiato un po', a seguito di DR 1686, a:

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

che riflette il fatto che constexpr implica la costante qualificazione per gli oggetti.


2. Risposta breve: per C++ 11 e 14, vedi sopra; per bozza C++ 1z, str3 non è un'espressione costante, poiché il puntatore stesso non è constexpr ed è anche l'indirizzo di una stringa letterale. str4 è costante, ma è pur sempre un indirizzo di una stringa letterale.

Risposta lunga:

Nella bozza di lavoro corrente, N4431, i vincoli non-argomenti di tipo di modello è stato rilassato. [14.3.2p1] ora dice:

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).

E quelle sono tutte le restrizioni. La parte di espressione costante convertita è piuttosto importante; la definizione completa è long, ma una parte rilevante per il nostro caso è che l'indirizzo di un oggetto con durata di memorizzazione statica è una tale espressione.

inoltre rilevante è che, secondo [5.20p2.7], un Ivalue a rvalue conversione applicato a

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

soddisfa anche le condizioni per essere un'espressione costante. Ciò ci consente di utilizzare alcune variabili puntatore constexpr come argomenti modello non di tipo. (Si noti che la semplice dichiarazione di una variabile const non è sufficiente, in quanto può essere inizializzata con un'espressione non costante.)

Quindi, qualcosa come constexpr const char* str3 = str1; va bene. È accettato da Clang 3.6.0 in modalità C++ 1z (e rifiutato in modalità C++ 14); GCC 5.1.0 lo rifiuta ancora - sembra che non abbia ancora implementato le regole aggiornate.


Ancora, cosa c'è di sbagliato con stringhe letterali? Ecco il problema (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.

L'attuazione è permesso di fare un sacco di cose con le stringhe: mix, match, renderli sovrappongono (interamente o parzialmente), fare 7 copie dal stessa unità di traduzione - qualunque cosa. Ciò rende l'indirizzo di una stringa letterale inutilizzabile come argomento modello non di tipo.

+1

Posso persino rendere il puntatore sia 'const' che' constexpr', vedere la modifica leggermente aggiornata, ancora lo stesso problema. Il fatto che sia l'indirizzo di una stringa letterale non dovrebbe essere un problema, purché abbia un collegamento esterno, quindi l'indirizzo è lo stesso su tutte le unità di traduzione. Ma lo standard dice no, vedo ... Credo che la mia domanda sia più simile a * "perché lo standard dice no?" * Grazie comunque per la citazione! – vsoftco

+0

Quindi i primi due esempi funzionano perché sono tecnicamente matrici di caratteri costruite da stringhe letterali e non stringhe letterali? – jaggedSpire

+0

@jaggedSpire Sì, è proprio così. – bogdan