Vaut mille mots:
#include<string>
#include<iostream>
class SayWhat {
public:
SayWhat& operator[](const std::string& s) {
std::cout<<"here\n"; // To make sure we fail on function entry
std::cout<<s<<"\n";
return *this;
}
};
int main() {
SayWhat ohNo;
// ohNo[1]; // Does not compile. Logic prevails.
ohNo[0]; // you didn't! this compiles.
return 0;
}
Le compilateur ne se plaint pas en passant le numéro 0 à l'opérateur de parenthèse acceptant une chaîne. Au lieu de cela, cela compile et échoue avant d'entrer dans la méthode avec:
terminate called after throwing an instance of 'std::logic_error'
what(): basic_string::_S_construct null not valid
Pour référence:
> g++ -std=c++17 -O3 -Wall -Werror -pedantic test.cpp -o test && ./test
> g++ --version
gcc version 7.3.1 20180303 (Red Hat 7.3.1-5) (GCC)
Ma conjecture
Le compilateur utilise implicitement le constructeur std::string(0)
pour entrer la méthode, ce qui génère le même problème (google l'erreur ci-dessus) sans raison valable.
Question
Y a-t-il un moyen de résoudre ce problème côté classe, afin que l'utilisateur de l'API ne le ressente pas et que l'erreur soit détectée au moment de la compilation?
Autrement dit, ajouter une surcharge
void operator[](size_t t) {
throw std::runtime_error("don't");
}
n'est pas une bonne solution.
La raison pour laquelle std::string(0)
est valide est due au fait que 0
Est une constante de pointeur nulle. Donc 0 correspond au constructeur de chaîne prenant un pointeur. Ensuite, le code va à l'encontre de la condition préalable selon laquelle on ne peut pas passer un pointeur nul à std::string
.
Seul le littéral 0
Serait interprété comme une constante de pointeur nul, s'il s'agissait d'une valeur d'exécution dans un int
vous n'auriez pas ce problème (car alors la résolution de surcharge chercherait un int
conversion à la place). Le littéral 1
N'est pas non plus un problème, car 1
N'est pas une constante de pointeur nul.
Puisqu'il s'agit d'un problème de temps de compilation (valeurs littérales invalides), vous pouvez l'attraper au moment de la compilation. Ajoutez une surcharge de ce formulaire:
void operator[](std::nullptr_t) = delete;
std::nullptr_t
Est le type de nullptr
. Et cela correspondra à any constante du pointeur nul, que ce soit 0
, 0ULL
Ou nullptr
. Et puisque la fonction est supprimée, elle provoquera une erreur de temps de compilation pendant la résolution de surcharge.
Une option consiste à déclarer une surcharge private
de operator[]()
qui accepte un argument intégral et ne le définit pas.
Cette option fonctionnera avec toutes les normes C++ (à partir de 1998), contrairement aux options comme void operator[](std::nullptr_t) = delete
qui sont valides à partir de C++ 11.
Faire de la operator[]()
un membre private
provoquera une erreur diagnostiquable sur votre exemple ohNo[0]
, sauf si cette expression est utilisée par une fonction membre ou friend
de la classe.
Si cette expression est utilisée à partir d'une fonction membre ou friend
de la classe, le code sera compilé mais - puisque la fonction n'est pas définie - généralement la construction échouera (par exemple une erreur de l'éditeur de liens due à une fonction non définie).