27 votes

Les littéraux de chaîne ne sont pas autorisés en tant que paramètres de modèle non type

La citation suivante est extraite des modèles C ++ d'Addison Wesley . Quelqu'un pourrait-il m'aider à comprendre en termes simples anglais / profane son essence?

Étant donné que les littéraux de chaîne sont des objets avec une liaison interne (deux littéraux de chaîne ayant la même valeur mais dans des modules différents sont des objets différents), vous ne pouvez pas non plus les utiliser comme arguments de modèle:

39voto

GManNickG Points 155079

Votre compilateur en fin de compte fonctionne sur des choses que l'on appelle les unités de traduction, officieusement appelé les fichiers source. Au sein de ces unités de traduction, vous identifiez les différentes entités: des objets, des fonctions, etc. Les linkers rôle est de relier ces unités, et une partie de ce processus est la fusion des identités.

Les identificateurs de liaison: une liaison interne signifie que l'entité nommée dans cette unité de traduction est visible uniquement pour l'unité de traduction, tandis que la liaison externe signifie que l'entité est visible par les autres unités.

Lorsqu'une entité est marquée static,, elle est donnée interne de liaison. Donc, compte tenu de ces deux unités de traduction:

// a.cpp
static void foo() { /* in a */ } 

// b.cpp
static void foo() { /* in a */ } 

Chacun de ceux - foos'fait référence à une entité (une fonction dans ce cas) qui n'est visible que pour leur respective des unités de traduction; c'est, chaque unité de traduction a sa propre foo.

Le hic, c'est, ensuite: les littéraux de chaîne sont du même type que static const char[..]. C'est:

// str.cpp
#include <iostream>

// this code:

void bar()
{
    std::cout << "abc" << std::endl;
}

// is conceptually equivalent to:

static const char[4] __literal0 = {'a', 'b', 'c', 0};

void bar()
{
    std::cout << __literal0 << std::endl;
}

Et comme vous pouvez le voir, la traduction littérale de la valeur interne de l'unité de traduction. Donc, si vous utilisez "abc" dans plusieurs unités de traduction, par exemple, tous finissent par être différentes entités.

Dans l'ensemble, cela signifie que ce est conceptuellement pas de sens:

template <const char* String>
struct baz {};

typedef baz<"abc"> incoherent;

Parce qu' "abc" est différent pour chaque unité de traduction. Chaque unité de traduction sera différente de la classe parce que chaque "abc" est une entité différente, même si elles ont fourni le "même" argument.

Sur le niveau de langue, c'est imposé en disant que le modèle non-type de paramètres peuvent être des pointeurs vers des entités de liaison externe; c'est, des choses qui ne se réfèrent à la même entité à travers les unités de traduction.

Donc c'est très bien:

// good.hpp
extern const char* my_string;

// good.cpp
const char* my_string = "any string";

// anything.cpp
typedef baz<my_string> coherent; // okay; all instantiations use the same entity

†Pas tous les identificateurs de liaison; certains n'en ont aucun, tels que les paramètres de la fonction.

‡ Un compilateur optimisant va stocker identiques littéraux à la même adresse, pour économiser de l'espace; mais c'est une qualité de mise en œuvre détail, pas une garantie.

10voto

Tony D Points 43962

Cela signifie que vous ne pouvez pas faire ça ...

 #include <iostream>

template <const char* P>
void f() { std::cout << P << '\n'; }

int main()
{
    f<"hello there">();
}
 

... parce que "hello there" n'est pas garanti à 100% pour se résoudre à une seule valeur intégrale qui peut être utilisée pour instancier le modèle une fois (bien que la plupart des bons éditeurs de liens tenteront de replier toutes les utilisations sur les objets liés et de produire un nouvel objet avec une seule copie de la chaîne).

Vous pouvez cependant utiliser des tableaux / pointeurs de caractères externes:

 ...
extern const char p[];
const char p[] = "hello";
...
    f<p>();
...
 

6voto

Mikael Persson Points 7174

Évidemment, les littéraux de chaîne comme "foobar" ne sont pas comme les autres littéral de type intégré (comme int ou float). Ils ont besoin d'avoir une adresse (const char*). L'adresse est vraiment la valeur de la constante que le compilateur des substituts à la place d'où le sens littéral apparaît. Que l'adresse des points de nulle part, fixé au moment de la compilation, dans la mémoire du programme.

Il doit être d'une liaison interne à cause de cela. Une liaison interne signifie simplement qu'il ne peut être lié à travers les unités de traduction (compilé les fichiers cpp). Le compilateur pourrait essayer de le faire, mais n'est pas tenu de le faire. En d'autres termes, une liaison interne signifie que si vous avez pris l'adresse de deux identiques chaînes littérales (c'est à dire la valeur de la const char* ils se traduisent par des) dans les différents fichiers cpp, ils ne seraient pas les mêmes, en général.

Vous ne pouvez pas les utiliser comme paramètres de modèle car ils auront besoin d'un strcmp() pour vérifier qu'ils sont les mêmes. Si vous avez utilisé l' ==, vous serait tout simplement en comparant les adresses, ce qui ne serait pas le même lorsque le modèle sont instanciés avec la même chaîne de caractères dans les différentes unités de traduction.

D'autres plus simples types intégrés, comme des littéraux, sont également une liaison interne (ils n'ont pas un identifiant et ne peuvent pas être liés de différentes unités de traduction). Cependant, leur comparaison est triviale, c'est par la valeur. Donc ils peuvent être utilisés pour les modèles.

2voto

Syhon Points 105

Comme mentionné dans d'autres réponses, d'un littéral de chaîne ne peut pas être utilisé comme un argument de modèle. Cependant, il existe une solution de contournement qui a un effet similaire, mais la "chaîne" est limité à quatre caractères. Cela est dû à multi-constantes de caractère qui, comme expliqué dans le lien, sont probablement peu portables, mais il a travaillé pour mes fins de débogage.

template<int32_t nFourCharName>
class NamedClass
{
    std::string GetName(void) const
    {
        // Evil code to extract the four-character name:
        const char cNamePart1 = static_cast<char>(static_cast<uint32_t>(nFourCharName >> 8*3) & 0xFF);
        const char cNamePart2 = static_cast<char>(static_cast<uint32_t>(nFourCharName >> 8*2) & 0xFF);
        const char cNamePart3 = static_cast<char>(static_cast<uint32_t>(nFourCharName >> 8*1) & 0xFF);
        const char cNamePart4 = static_cast<char>(static_cast<uint32_t>(nFourCharName       ) & 0xFF);

        std::ostringstream ossName;
        ossName << cNamePart1 << cNamePart2 << cNamePart3 << cNamePart4;
        return ossName.str();
    }
};

Peut être utilisé avec:

NamedClass<'Greg'> greg;
NamedClass<'Fred'> fred;
std::cout << greg.GetName() << std::endl;  // "Greg"
std::cout << fred.GetName() << std::endl;  // "Fred"

Comme je l'ai dit, c'est une solution de contournement. Je n'ai pas semblant de croire que c'est bon, propre, code portable, mais d'autres peuvent trouver utile. Une autre solution pourrait impliquer plusieurs char arguments de modèle, comme dans cette réponse.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X