web-dev-qa-db-fra.com

Le déplacement d'une fonction membre de la classe de base vers la classe dérivée interrompt le programme sans raison évidente

Cette question (inventée) a été initialement formulée comme un puzzle, cachant certains des détails qui pourraient aider à voir le problème plus rapidement. Faites défiler vers le bas pour la version plus simple MCVE .


Version originale (a-la puzzle)

J'ai ce morceau de code qui génère 0:

#include <iostream>
#include <regex>

using namespace std;

regex sig_regex("[0-9]+");
bool oldmode = false;

template<class T>
struct B
{
    T bitset;

    explicit B(T flags) : bitset(flags) {}

    bool foo(T n, string s)
    {
        return bitset < 32                   // The mouth is not full of teeth
               && 63 > (~n & 255) == oldmode // Fooness holds
               && regex_match(s, sig_regex); // Signature matches
    }
};

template<class T>
struct D : B<T>
{
    D(T flags) : B<T>(flags) {}

};

int main()
{
    D<uint64_t> d(128 | 16 | 1);
    cout << d.foo(7, "123") << endl;
}

Cependant, lorsque je déplace la fonction foo() de B vers D, elle commence à afficher 1 ( la preuve est sur Colir ) .

Pourquoi cela arrive-t-il?


Version MCVE

En direct sur Coliru

#include <iostream>
#include <bitset>

using namespace std;

template<class T>
struct B
{
    T bitset{0};

    bool foo(int x)
    {
        return bitset < 32 && 63 > (x + 1) == x % 2;
    }
};

template<class T>
struct D : B<T>
{
    bool bar(int x) // This is identical to B<T>::foo()
    {
        return bitset < 32 && 63 > (x + 1) == x % 2;
    }
};

int main()
{
    D<uint64_t> d;
    cout << d.foo(1) << endl; // outputs 1
    cout << d.bar(1) << endl; // outputs 0; So how is bar() different from foo()?
}
51
Leon

C'est pourquoi vous ne devriez jamais using namespace std;

bool foo(T n, string s)
{
    return bitset < 32                  
           && 63 > (~n & 255) == oldmode 
           && regex_match(s, sig_regex);
}

Ce bitset n'est pas ce que vous pensez. Car B<T> est une classe de base dépendante, les membres sont masqués de la recherche non qualifiée. Donc, pour accéder à bitset, vous devez y accéder via this1, ou le qualifier explicitement (voir ici pour plus de détails):

(this->bitset)
B<T>::bitset

Parce que bitset ne nomme pas B<T>::bitset dans le cas dérivé, qu'est-ce que cela pourrait signifier? Eh bien, parce que vous avez écrit using namespace std;, c'est en fait std::bitset, et le reste de votre expression se trouve être valide. Voici ce qui se passe:

bool foo(T n, string s)
{
    return std::bitset<32 && 63>(~n & 255) == oldmode 
           && regex_match(s, sig_regex);
}

Le 32 && 63 est évalué en true, qui est promu en 1u pour le std::bitset argument de modèle. Cette std::bitset est initialisé avec ~n & 255, et son égalité est vérifiée avec oldmode. Cette dernière étape est valide car std::bitset possède un constructeur non explicite qui autorise un std::bitset<1> à construire à partir de oldmode.


1 Notez que nous devons mettre entre parenthèses this->bitset dans ce cas en raison de règles de désambiguïté d'analyse assez subtiles. Voir le membre de base dépendant du modèle n'est pas résolu correctement pour plus de détails.

90
TartanLlama

Oui, car bitset sera interprété comme un nom non dépendant et il existe un modèle nommé std::bitset<T>, par conséquent, il sera analysé comme:

template<class T>
struct D : B<T>
{
    D(T flags) : B<T>(flags) {}
    bool foo(T n, string s)
    {
        return ((std::bitset < 32  && 63 > (~n & 255)) == oldmode)
               && regex_match(s, sig_regex);
    }
};

Vous devez le faire comme ceci:

template<class T>
struct D : B<T>
{
    D(T flags) : B<T>(flags) {}

    bool foo(T n, string s)
    {
        // or return B<T>::bitset
        return (this->B<T>::bitset < 32)                   // The mouth is not full of teeth
               && 63 > (~n & 255) == oldmode // Fooness holds
               && regex_match(s, sig_regex); // Signature matches
    }
};

ou mieux, n'utilisez pas using namespace std;

18
Danh
  1. Pourquoi cela arrive-t-il?

Pour la classe dérivée, B<T> n'est pas une classe de base non dépendante, elle ne peut pas être déterminée sans connaître l'argument du modèle. Et bitset est un nom non dépendant, qui ne sera pas recherché dans la classe de base dépendante. Au lieu, std::bitset est utilisé ici (en raison de using namespace std;). Vous obtiendrez donc:

return std::bitset<32 && 63>(~n & 255) == oldmode
       && regex_match(s, sig_regex);

Vous pouvez rendre le nom bitset dépendant; parce que les noms dépendants ne peuvent être recherchés qu'au moment de l'instanciation, et à ce moment-là la spécialisation de base exacte qui doit être explorée sera connue. Par exemple:

return this->bitset < 32                   // The mouth is not full of teeth
//     ~~~~~~
       && 63 > (~n & 255) == oldmode       // Fooness holds
       && regex_match(s, sig_regex);       // Signature matches

ou

return B<T>::bitset < 32                   // The mouth is not full of teeth
//     ~~~~~~
       && 63 > (~n & 255) == oldmode       // Fooness holds
       && regex_match(s, sig_regex);       // Signature matches

ou

using B<T>::bitset;
return bitset < 32                   // The mouth is not full of teeth
       && 63 > (~n & 255) == oldmode // Fooness holds
       && regex_match(s, sig_regex); // Signature matches
  1. Quel devrait être le titre de cette question, après y avoir répondu?

"Comment accéder aux noms non dépendants dans la classe de base du modèle?"

8
songyuanyao

Ceci est un exemple vraiment cool !!! :)

Je pense - ce qui se passe est le suivant:

bitset < 32 && 63 >(~n & 255)

Is parses as construct me a bitset

3
Nim