J'ai quelques confusions concernant static constexpr
variables membres en C++ 11.
template<typename T>
struct cond_I
{ static constexpr T value = 0; };
// specialization
template<typename T>
struct cond_I< std::complex<T> >
{ static constexpr std::complex<T> value = {0,1}; };
cout << cond_I<double>::value << endl; // this works fine
cout << cond_I< complex<double> >::value << endl; // linker error
Cependant, si j'ajoute la ligne suivante à first.hpp
tout fonctionne bien.
template<typename T1>
constexpr std::complex<T1> cond_I< std::complex<T1> >::value;
Ce que je comprends (je peux me tromper), c'est que cond_I< std::complex<double> >::value
a besoin d'une définition, mais dans le cas précédent, il n'a que la déclaration. Mais qu'en est-il de cond_I<double>::value
? Pourquoi ne nécessite-t-il pas de définition?
Encore une fois, dans un autre fichier d'en-tête, second.hpp
, J'ai:
// empty struct
template<typename T>
struct eps
{ };
// special cases
template<>
struct eps<double>
{
static constexpr double value = 1.0e-12;
};
template<>
struct eps<float>
{
static constexpr float value = 1.0e-6;
};
Dans ce cas, les codes suivants fonctionnent parfaitement sans aucune définition de eps<>::value
.
cout << eps<double>::value << endl; // works fine
cout << eps<float>::value << endl; // works fine
Quelqu'un peut-il m'expliquer les différents comportements de static constexpr
les variables membres, dans ces scénarios?
Ces comportements sont également les mêmes pour gcc-5.2
et clang-3.6
.
Selon la norme 9.4.2/p3 Membres de données statiques [class.static.data] (Emphasis Mine):
Si un membre de données stat const non volatile est de type intégral ou énumération, sa déclaration dans la définition de classe peut spécifier un initialiseur d'accolade ou égal dans lequel chaque clause d'initialisation qui est une expression d'affectation est une expression constante (5.20 ). n membre de données statiques de type littéral peut être déclaré dans la définition de classe avec le spécificateur
constexpr
; si tel est le cas, sa déclaration doit spécifier un initialiseur d'accolade ou égal dans lequel chaque clause d'initialisation qui est une affectation-expression est une expression constante. [Remarque: dans ces deux cas, le membre peut apparaître dans des expressions constantes. - end note] Le membre doit toujours être défini dans une étendue d'espace de noms s'il est utilisé par odr (3.2) dans le programme et la définition de la portée de l'espace de noms ne doivent pas contenir d'initialiseur.
Comme M.M l'a expliqué précédemment dans les commentaires, ostream::operator<<(ostream&, const complex<T>&)
passe par référence, donc la valeur est considérée comme utilisée par odr dans le programme. Ainsi, comme l'exige le libellé ci-dessus, vous devez fournir une définition.
Maintenant que vous avez déjà découvert que les types fondamentaux sont passés par valeur, c'est pourquoi aucune définition n'est requise.