J'ai écrit un wrapper string_view
Léger pour un projet C++ 14, et avec MSVC 2017, il déclenche un static_assert
Au moment de la compilation, mais le même code au moment de l'exécution passe le standard assert
. Ma question est, est-ce un bogue du compilateur, un comportement indéfini manifeste ou autre chose entièrement?
Voici le code distillé:
#include <cassert> // assert
#include <cstddef> // size_t
class String_View
{
char const* m_data;
std::size_t m_size;
public:
constexpr String_View()
: m_data( nullptr ),
m_size( 0u )
{}
constexpr char const* begin() const noexcept
{ return m_data; }
constexpr char const* end() const noexcept
{ return m_data + m_size; }
};
void static_foo()
{
constexpr String_View sv;
// static_assert( sv.begin() == sv.end() ); // this errors
static_assert( sv.begin() == nullptr );
// static_assert( sv.end() == nullptr ); // this errors
}
void dynamic_foo()
{
String_View const sv;
assert( sv.begin() == sv.end() ); // this compiles & is optimized away
assert( sv.begin() == nullptr );
assert( sv.end() == nullptr ); // this compiles & is optimized away
}
Voici un lien Explorateur du compilateur que j'ai utilisé pour reproduire le problème.
D'après ce que je peux dire, ajouter ou soustraire 0
De n'importe quelle valeur de pointeur est toujours valide:
end()
etc.Solution:
Si je change ma méthode end
comme suit, les static_assert
S défaillants passeront.
constexpr char const* end() const noexcept
{ return ( m_data == nullptr
? m_data
: m_data + m_size ); }
Bricolage:
J'ai pensé que l'expression m_data + m_size
Elle-même était peut-être UB, avant que m_size == 0
Soit évalué. Pourtant, si je remplace l'implémentation de end
par le non-sens return m_data + 0;
, Cela génère toujours les deux erreurs static_assert
. : - /
Mise à jour:
Cela semble être un bogue du compilateur qui a été corrigé entre 15.7 et 15.8.
Cela ressemble à un bogue MSVC, le projet de norme C++ 14 permet explicitement d'ajouter et de soustraire la valeur 0
à un pointeur à comparer égal à lui-même, de [expr.add] p7 :
Si la valeur 0 est ajoutée ou soustraite d'une valeur de pointeur, le résultat est égal à la valeur de pointeur d'origine. Si deux pointeurs pointent vers le même objet ou les deux pointent au-delà de la fin du même tableau ou si les deux sont nuls et que les deux pointeurs sont soustraits, le résultat est égal à la valeur 0 convertie au type std :: ptrdiff_t.
Il semble défaut CWG 1776 conduire à p0137 qui a ajusté [expr.add] p7 pour dire explicitement null pointer
.
Le dernier projet a rendu cela encore plus explicite [expr.add] p4 :
Lorsqu'une expression J de type intégral est ajoutée ou soustraite d'une expression P de type pointeur, le résultat a le type P.
- Si P est évalué à une valeur de pointeur nulle et J est évalué à 0, le résultat est une valeur de pointeur nulle.
- Sinon, si P pointe vers l'élément x [i] d'un objet tableau x avec n éléments, 85 les expressions P + J et J + P (où J a la valeur j) pointent vers (éventuellement- élément hypothétique) x [i + j] si 0≤i + j≤n et l'expression P - J pointe vers l'élément (éventuellement hypothétique) x [i − j] si 0≤i − j≤n. (4.3).
- Sinon, le comportement n'est pas défini.
Cette modification a été effectuée sur le plan éditorial, voir ce problème github et ce PR .
MSVC est incohérent ici en ce qu'il permet d'ajouter et de soustraire zéro dans une expression constante, tout comme gcc et clang. Ceci est essentiel car n comportement indéfini dans une expression constante est mal formé et nécessite donc un diagnostic. Compte tenu des éléments suivants:
constexpr int *p = nullptr ;
constexpr int z = 0 ;
constexpr int *q1 = p + z;
constexpr int *q2 = p - z;
gcc, clang et MSVC lui permettent une expression constante ( exemple live godbolt ) bien que malheureusement MSVC soit doublement incohérent en ce qu'il autorise également une valeur non nulle, étant donné ce qui suit:
constexpr int *p = nullptr ;
constexpr int z = 1 ;
constexpr int *q1 = p + z;
constexpr int *q2 = p - z;
clang et gcc disent qu'il est mal formé alors que MSVC ne le fait pas ( live godbolt ).
Je pense que c'est certainement un bug dans la façon dont MSVC évalue les expressions constantes, car GCC et Clang n'ont aucun problème avec le code, et la norme est claire que l'ajout de 0 à un pointeur nul donne un pointeur nul ([expr.add]/sept).