将成员函数从基类移动到派生类会导致程序无法正常运行
这个(制造)的问题最初是作为一个谜题,隐藏了一些可能帮助更快地看到问题的细节。 向下滚动以获得更简单的MCVE版本。
原始(a-la难题)版本
我有这段代码输出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()? }
这就是为什么你不应该using namespace std;
bool foo(T n, string s) { return bitset < 32 && 63 > (~n & 255) == oldmode && regex_match(s, sig_regex); }
这不符合你的想法。 因为B<T>
是一个从属基类,所以成员对不合格的查找是隐藏的。 所以要访问bitset
,你需要通过this
1来访问它,或者明确地限定它(参见这里了解更多细节):
(this->bitset) B<T>::bitset
由于bitset
在派生的情况下不会命名B<T>::bitset
,这意味着什么? 那么,因为你using namespace std;
写了using namespace std;
,它实际上是std::bitset
,其余的expression式恰好是有效的。 以下是发生的事情:
bool foo(T n, string s) { return std::bitset<32 && 63>(~n & 255) == oldmode && regex_match(s, sig_regex); }
32 && 63
计算结果为true
,为std::bitset
模板参数提升为1u
。 这个std::bitset
被初始化为oldmode
~n & 255
,并被检查与oldmode
是否相等。 最后一步是有效的,因为std::bitset
有一个非显式的构造函数,它允许从oldmode
构造一个临时std::bitset<1>
。
1注意,在这种情况下,我们需要用括号括起this->bitset
,因为有一些非常细微的parsing消歧规则。 有关详细信息,请参阅模板相关的基本成员未正确parsing
是的,因为bitset
将被解释为非依赖名称,并且有一个名为std::bitset<T>
的模板,因此它将被parsing为:
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;
- 为什么会这样呢?
对于派生类, B<T>
不是非独立基类,不知道模板参数就不能确定。 bitset
是一个不依赖的名字,不会在依赖的基类中查找。 相反,这里使用了std::bitset
(因为using namespace std;
)。 所以你会得到:
return std::bitset<32 && 63>(~n & 255) == oldmode && regex_match(s, sig_regex);
你可以使名字取决于位置; 因为依赖名称只能在实例化的时候查找,那时必须知道必须探究的确切的基础专业化。 例如:
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
- 这个问题的标题应该是什么?
“如何访问模板基类中的非依赖名称?
这是一个非常酷的例子! 🙂
我想 – 正在发生的是这样的:
bitset < 32 && 63 >(~n & 255)
分析,构build我一bitset