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


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


оригинал (а-ля головоломка) версия

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

жить на 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()?
}
4 51

4 ответа:

вот почему вы никогда не должны 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, вам необходимо получить к нему доступ через this1, или явно укажите это (см. здесь для более подробной информации):

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

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


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

Да, потому что 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;

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

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

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

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

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

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

разбирает как построить мне bitset