【问题标题】:Moving a member function from base class to derived class breaks the program for no obvious reason将成员函数从基类移动到派生类会无缘无故地破坏程序
【发布时间】:2017-03-20 15:28:49
【问题描述】:

这个(虚构的)问题最初被表述为一个谜题,隐藏 一些可能有助于更快地发现问题的细节。向下滚动 对于更简单的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 (proof is on 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()?
}

【问题讨论】:

  • foo 中的return 表达式的哪一位发生了变化?
  • 这是一个有效的主题问题;关闭投票不正确。
  • 代码示例是否应该是不必要的非最小化和混乱?如果没有,我建议只包含&lt;bitset&gt; 并摆脱整个regex red herring。
  • 您应该在发布之前尝试简化此代码。
  • "[...] 这是否意味着作为测试 [...]" --> "否[...]" --> "[...] 问题最初是制定的作为一个谜题[...]”。我不知道 stackoverflow 是否适合在不回答的情况下提出您已经知道答案的问题,同时让发现问题变得更加困难。

标签: c++ c++11 templates namespaces


【解决方案1】:

是的,因为bitset会被解释为非依赖名,并且有一个名为std::bitset&lt;T&gt;的模板,所以会被解析为:

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;

【讨论】:

    【解决方案2】:

    这是一个非常酷的例子!!! :)

    我认为 - 发生的事情是这样的:

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

    被解析为bitset

    【讨论】:

      【解决方案3】:
      1. 为什么会这样?

      对于派生类,B&lt;T&gt;不是非依赖基类,不知道模板参数是无法确定的。而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】:

        这就是为什么你永远不应该using namespace std;

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

        bitset 不是你想的那样。因为B&lt;T&gt; 是一个从属基类,所以对非限定查找隐藏了成员。所以要访问bitset,你需要通过this1来访问它,或者明确限定它(更多细节见here):

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

        因为bitset 在派生的情况下没有命名B&lt;T&gt;::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 &amp;&amp; 63 的计算结果为 true,对于 std::bitset 模板参数,它被提升为 1u。这个std::bitset~n &amp; 255 初始化,并用oldmode 检查是否相等。最后一步是有效的,因为std::bitset 有一个非显式构造函数,它允许从oldmode 构造一个临时的std::bitset&lt;1&gt;


        1 请注意,在这种情况下,由于一些非常微妙的解析消歧规则,我们需要用括号括起来 this-&gt;bitset。详情请见Template dependent base member is not resolved properly

        【讨论】:

        • "... 而你的其他表达恰好是有效的" 你真的相信这确实是巧合吗? (:
        • @Leon 为什么,这是一个测试而不是一个实际问题?
        • 不,这是为了演示 C++ 是如何依赖于上下文的。您对“问题”的解释很好。
        • @TartanLlama this-&gt;bitset 也不编译,它看起来像显式名称 qual。 B&lt;T&gt;::bitset 是唯一的出路。至少 gcc6.2 仍然认为this-&gt;bitset 指的是`std::bitset'。
        • @vsoftco:这是相当令人惊讶的行为 :(
        猜你喜欢
        • 2023-03-10
        • 1970-01-01
        • 2017-06-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-23
        相关资源
        最近更新 更多