Подтвердить что ты не робот

Перемещение функции-члена из базового класса в производный класс разрывает программу без видимой причины

Этот (составленный) вопрос первоначально был сформулирован как головоломка, скрывающая некоторые детали, которые могут помочь быстрее увидеть проблему. Прокрутить вниз для более простой MCVE версии.


Оригинальная версия (a-la puzzle)

У меня есть эта часть кода, которая выводит 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;
}

Однако, когда я перемещаю функцию foo() от B до D, она начинает выводить 1 (доказательство находится на Coliru).

Почему это происходит?


версия MCVE

Live on 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()?
}
4b9b3361

Ответ 1

Вот почему вы никогда не должны using namespace std;

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

Это bitset не то, что вы думаете. Поскольку B<T> является зависимым базовым классом, члены скрыты от неквалифицированного поиска. Поэтому для доступа к bitset вам нужно получить доступ к нему через this 1 или явно его квалифицировать (см. здесь для получения дополнительной информации)

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

Потому что bitset не называет B<T>::bitset в производном случае, что это значит? Ну, потому что вы написали using namespace std;, это на самом деле std::bitset, а остальная часть вашего выражения просто так будет действительна. Вот что происходит:

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

32 && 63 оценивается как true, что соответствует 1u для аргумента шаблона std::bitset. Этот std::bitset инициализируется с помощью ~n & 255 и проверяется на равенство с oldmode. Этот последний шаг действителен, поскольку std::bitset имеет неявный конструктор, который позволяет создать временный std::bitset<1> из oldmode.


1 Обратите внимание, что в этом случае нам нужно заключить в скобки this->bitset из-за некоторых довольно тонких правил разборчивости разбора. См. Базовый элемент, зависящий от шаблона, не разрешен правильно для получения подробной информации.

Ответ 2

Да, поскольку bitset будет интерпретироваться как не зависящее имя, и есть шаблон с именем std::bitset<T>, следовательно, он будет анализироваться как:

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

Вам нужно сделать следующее:

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

или лучше, не используйте using namespace std;

Ответ 3

  • Почему это происходит?

Для производного класса B<T> не является независимым базовым классом, он не может быть определен без знания аргумента шаблона. И bitset - это независимое имя, которое не будет искать в зависимом базовом классе. Вместо этого используется std::bitset (из-за using namespace std;). Итак, вы получите:

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

Вы можете указать имя bitset зависимым; потому что зависимые имена могут быть просмотрены только во время создания экземпляра, и в то время будет известна точная базовая специализация, которая должна быть изучена. Например:

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

или

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

или

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. Каким должно быть название этого вопроса после ответа?

"Как получить доступ к независящим именам в базовом классе шаблона?"

Ответ 4

Это действительно классный пример!!!:)

Я думаю - что происходит:

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

Является ли анализ методом a bitset