Mover una función miembro de la clase base a la clase derivada rompe el programa sin motivo aparente

6 minutos de lectura

avatar de usuario
León

Esta pregunta (inventada) se formuló inicialmente como un rompecabezas, ocultando algunos de los detalles que podrían ayudar a ver el problema más rápido. Desplácese hacia abajo para ver la versión MCVE más simple.


Versión original (a-la puzzle)

Tengo este fragmento de código que genera 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;
}

Sin embargo, cuando muevo la función foo() de B a D comienza a emitir 1 (la prueba está en Coliru).

¿Por qué pasó esto?


versión MCVE

Vive en 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()?
}

  • ¿Qué parte de la return expresión en foo ¿cambios?

    – Betsabé

    7 de noviembre de 2016 a las 10:18

  • Esta es una pregunta válida sobre el tema; el voto para cerrar es incorrecto.

    – smci

    8 de noviembre de 2016 a las 2:04

  • ¿Se supone que la muestra de código es innecesariamente no mínima y confusa? Si no, sugeriría simplemente incluir <bitset> y deshacerse de todo regex cortina de humo.

    – Cactus

    8 de noviembre de 2016 a las 4:31

  • Debería haber intentado simplificar este código antes de publicarlo.

    – Stig Hemmer

    8 de noviembre de 2016 a las 8:50

  • “[…] ¿Esto es una prueba? […]” –> “No[…]” –> “[…] la pregunta se formuló inicialmente como un rompecabezas[…]”. No sé si stackoverflow es el lugar para hacer preguntas de las que ya sabe la respuesta sin responderlas, lo que hace que sea más difícil detectar el problema.

    – Pixelquímico

    8 de noviembre de 2016 a las 9:35

avatar de usuario
TartánLlama

Por eso nunca debes using namespace std;

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

Que bitset no es lo que crees que es. Porque B<T> es una clase base dependiente, los miembros están ocultos de la búsqueda no calificada. Entonces para acceder bitsetnecesitas acceder a él a través de this1o calificarlo explícitamente (ver aquí para más detalles):

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

Porque bitset no nombra B<T>::bitset en el caso derivado, ¿qué podría significar? Bueno, porque escribiste using namespace std;en realidad es std::bitset, y el resto de su expresión resulta ser válida. Esto es lo que sucede:

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

los 32 && 63 evalúa a trueque es ascendido a 1u Para el std::bitset argumento de la plantilla. Este std::bitset se inicializa con ~n & 255y se verifica la igualdad con oldmode. Este último paso es válido porque std::bitset tiene un constructor no explícito que permite un temporal std::bitset<1> ser construido a partir de oldmode.


1 Tenga en cuenta que tenemos que poner entre paréntesis this->bitset en este caso debido a algunas reglas de desambigüedad de análisis bastante sutiles. Consulte El miembro base dependiente de la plantilla no se resolvió correctamente para obtener más detalles.

  • “… y el resto de tu expresión resulta ser válida” ¿De verdad crees que esto es realmente una coincidencia? (:

    – León

    7 de noviembre de 2016 a las 10:33

  • @Leon ¿Por qué, esto se entiende como una prueba en lugar de una pregunta real?

    – TartánLlama

    7 de noviembre de 2016 a las 10:34

  • No, pretendía ser una demostración de cómo depende el contexto de C++. Muy buena tu explicación del “problema”.

    – León

    7 de noviembre de 2016 a las 10:43

  • @TartanLlama this->bitset tampoco compila, parece una calificación de nombre explícito. B<T>::bitset es el único camino a seguir. Al menos gcc6.2 todavía cree this->bitset se refiere a `std::bitset’.

    – vsoftco

    7 noviembre 2016 a las 12:50


  • @vsoftco: Ese es un comportamiento bastante sorprendente 🙁

    – Matthieu M.

    7 de noviembre de 2016 a las 16:10

avatar de usuario
danh

Sí porque bitset se interpretará como un nombre no dependiente, y hay una plantilla denominada std::bitset<T>por lo tanto, se analizará como:

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);
    }
};

Tienes que hacerlo así:

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
    }
};

o mejor, no uses using namespace std;

avatar de usuario
cancionyuanyao

  1. ¿Por qué pasó esto?

Para la clase derivada, B<T> no es una clase base no dependiente, no se puede determinar sin conocer el argumento de la plantilla. Y bitset es un nombre no dependiente, que no se buscará en la clase base dependiente. En cambio, std::bitset se utiliza aquí (debido a using namespace std;). Entonces obtendrás:

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

Podrías hacer el nombre bitset dependiente; porque los nombres dependientes se pueden buscar solo en el momento de la creación de instancias, y en ese momento se sabrá la especialización base exacta que se debe explorar. Por ejemplo:

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

o

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

o

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. ¿Cuál debería ser el título de esta pregunta, una vez respondida?

“¿Cómo acceder a los nombres no dependientes en la clase base de la plantilla?”

¡¡¡Este es un ejemplo genial!!! 🙂

Creo que lo que está pasando es esto:

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

¿Se analiza como construirme un bitset

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad