将成员函数从基类移动到派生类会导致程序无法正常运行

这个(制造)的问题最初是作为一个谜题,隐藏了一些可能帮助更快地看到问题的细节。 向下滚动以获得更简单的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;

  1. 为什么会这样呢?

对于派生类, 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 
  1. 这个问题的标题应该是什么?

“如何访问模板基类中的非依赖名称?

这是一个非常酷的例子! 🙂

我想 – 正在发生的是这样的:

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

分析,构build我一bitset