什么是"结构体黑科技"和"type/non-type hiding"?

13

我在cppreference上看到了这个。

在某个作用域中查找名称会找到该名称的所有声明,但有一个例外,称为“结构体Hack”或“类型/非类型隐藏”:在同一作用域内,某些名称的出现可能是指不是typedefclass / struct / union / enum的声明,而同一名称的所有其他出现要么都是指向同一变量、非静态数据成员(自C++14起),枚举值,要么都是指向可能重载的函数或函数模板名称

上述文本的链接在这里

我不理解什么是“struct hack”和“type/non-type hiding”。

它们是相同的概念吗?你能给一个简单的解释吗?最好有演示片段。

2个回答

7
这句话的真正含义应该理解为:
引起名称查找异常的例外情况涉及到“struct hack”,也称为“类型/非类型隐藏”。
因此,您要查找的概念的定义实际上是“类型/非类型隐藏”。
术语“struct hack”可能会让人感到困惑,因为它指的是C灵活数组,这是C特定实现,而不是名称查找问题。
关于“类型/非类型隐藏”,它允许您编写并编译以下内容:
#include <iostream>

namespace first
{
class vector
{
    public:
    int hidden;
};
}

namespace second {
  using namespace first;
  class vector
  {
      public:
      int visible;
  };
  double f()
  {
      vector f;
      f.visible=2;
      int vector = f.visible;
      return vector;
  }
};

int main() {
  std::cout << second::f() << std::endl;
}

在godbolt.org上展示

正如您所看到的,second::vectorfirst::vector 隐藏在 namespace second 的作用域内。
此外,在函数 f 内部,int vector 会隐藏 second::vector
这个概念在 IBM thread 中得到了很好的解释:

如果一个类名或枚举名在作用域中没有被隐藏,则它是可见的。一个类名或枚举名可以被同名的对象、函数或枚举器的显式声明隐藏在嵌套的声明区域或派生类中。无论何时该对象、函数或枚举器名称可见时,类名或枚举名都会被隐藏。这个过程被称为名称隐藏。

在成员函数定义中,局部名称的声明会隐藏与其同名的类成员的声明。派生类中成员的声明会隐藏同名基类成员的声明。

你也可以查看ISO CPP标准:
6.3.10 名称隐藏[basic.scope.hiding]http://eel.is/c++draft/basic.scope.hiding

谢谢您的有用回答!我必须说,关键问题仍然存在。就像您上面所说的那样,“struct hack”也被称为“type/non-type hiding”。那么,“struct hack”和“type/non-type”之间有什么相似之处呢? - Guokas
我认为我们遇到了一个语义问题。在C++中,struct hack的含义完全不同于C99中的struct hack,后者实际上应该称为灵活数组(请参见http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf)。此外,请注意,C ++没有灵活的数组成员。我真的认为您可以建议网站cpprefrence删除“struct hack”部分。此外,在ISO C++标准中从未提及struct hack作为c++特性(请参见http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4713.pdf)。我已经编辑了我的答案以引用这篇论文。 - PilouPili
2
作用域和命名空间与问题主题无关。类型/非类型隐藏是指以下顺序:struct vector {}; int vector = 42;,其中两个定义在同一作用域中。 - n. m.

7

在最初,有C语言。在C语言中,像这样的声明是完全可能的(也确实很常见):

#include <time.h>  // defines struct tm { ... }
struct tm tm;

int stat(const char *pathname, struct stat *statbuf); // defined somewhere in POSIX headers

这段代码在C语言中是完全正常的,因为像“tm”或“stat”这样的标记并不代表类型。只有“struct tm”和“struct stat”才是类型。
#include <time.h> 
tm my_time; // doesn't work in C

进入 C++。在 C++ 中,如果你定义了 struct tm { ... };,那么 tm 单独就是一个类型名称。

#include <time.h>
tm my_time; // OK in C++

但是如果没有你引用中提到的“一个例外”,像上面那样的C代码将无法在C++编译器中编译。
#include <time.h>
struct tm tm; // would not compile without the exception 
              // because tm alone already refers to a type
              // defined in this scope

由于破坏完好的C代码并不是C++的意图,因此引入了异常并加以实施。它基本上表示您可以使用与类/结构体/联合标记相同名称的变量、函数和其他一些内容进行定义。如果这样做,则该标记仅在此范围内停止成为类型名称。

#include <time.h>
struct tm tm;      // compiles because of the exception
tm my_time;        // no longer compiles because `tm` variable hides the type
struct tm my_time; // OK

所以这就是“类型/非类型隐藏”(因为一种类型被一个非类型隐藏)的“技巧”。它被称为“技巧”,因为它是在一个完美平稳和无聊规则(“每个名称只引用一件事情”)中轻微弯曲,使得某些事情(与旧C代码的兼容性)成为可能。正常的基于作用域的名称隐藏不是技巧。它是一件完全普通的事情,而不是任何东西的聪明弯曲。


我明白了,这是为了与C代码兼容而使用的C++。另外一个明确的事情是,所引用的class/struct/union/enum声明不能是typedef。这是因为C不参与typedef吗? - Guokas
@V.Wu 在C语言中也不能使用typedef来实现这个。 - n. m.
这句话的意思是,“某些名称的出现可能指的是类/结构体/联合体/枚举类型的声明,而不是typedef。”声明不能是typedef。这是因为C语言没有typedef吗? - Guokas
不,C语言有typedef。 - n. m.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接