Hexagon LLVM编译架构介绍(20)

240 篇文章 11 订阅
本文详细介绍了LLVM编译器在处理C++代码时与GCC的兼容性差异,包括对变长数组、模板中的非限定查找、类模板依赖基础的非限定查找、不完整类型、无效模板实例化以及类类型const变量默认初始化等问题的处理。通过实例展示了错误用法及修复建议,帮助开发者理解并解决相关编译错误。
摘要由CSDN通过智能技术生成

6.2 C++兼容性

6.2.1 变长数组

GCC 和 C99 允许在运行时确定数组的大小。 标准 C++ 中不允许此扩展名。 但是,为了与 GNU C 和 C99 程序兼容,LLVM 在非常有限的情况下支持此类可变长度数组:

  • 变长数组的元素类型必须是“普通旧数据”(POD) 类型,这意味着它不能有任何用户声明的构造函数或析构函数、任何基类或任何非POD 类型的成员。 所有 C 类型都是 POD 类型。
  • 变长数组不能用作非类型模板参数的类型。

如果您的代码以 LLVM 不支持的方式使用可变长度数组,则有几种方法可以修复您的代码:

  1. 如果可以在编译时确定合理的上限,则将变长数组替换为固定大小的数组; 有时这就像改变 int size = …; 一样简单。 const int size = …; (如果初始化程序是编译时常量);

  2. 使用 std::vector 或其他一些合适的容器类型; 或者

  3. 在堆上分配数组而不是使用 new Type[] – 只需记住delete [] 它。

6.2.2 模板中的非限定查找

某些版本的 GCC 接受以下无效代码:

template <typename T> T Squared(T x) {
  return Multiply(x, x);
}


int Multiply(int x, int y) {
  return x * y;
} 
int main() {
  Squared(5);
}

LLVM 使用以下消息标记此代码:

my_file.cpp:2:10: error: call to function 'Multiply' that is neither visible in the template definition nor found by argument-dependent lookup

   return Multiply(x, x);
          ^

my_file.cpp:10:3: note: in instantiation of function template specialization 'Squared<int>' requested here

  Squared(5);
  ^

my_file.cpp:5:5: note: 'Multiply' should be declared prior to the call site

  int Multiply(int x, int y) {

C++ 标准规定不合格的名称(例如“Multiply”)可以通过两种方式查找:

  • 首先,编译器在写入名称的作用域中执行非限定查找。 对于模板,这意味着查找是在定义模板的地方完成的,而不是在模板被实例化的地方。 因为此时 Multiply 还没有被声明,不合格的查找不会找到它。

  • 第二,如果名称像函数一样被调用,那么编译器也会进行参数相关查找(ADL)。 在 ADL 中,编译器查看调用的所有参数的类型。 当它找到一个类类型时,它会在该类的命名空间中查找名称; 结果是它在这些命名空间中找到的所有声明,以及来自非限定查找的声明。 但是,编译器在知道所有参数类型之前不会执行 ADL。

在上面的示例代码中,Multiply 是使用依赖参数调用的,因此在实例化模板之前不会完成 ADL。 此时,参数都具有 int 类型,它不包含任何类类型,因此 ADL 不会在任何命名空间中查找。 由于两种形式的查找都没有找到 Multiply 的声明,因此代码无法编译。

这是另一个示例,这次使用重载运算符,它们遵循非常相似的规则。

#include <iostream>

template<typename T>
void Dump(const T& value) {
  std::cout << value << "\n";

}
namespace ns {
  struct Data {}; 
} 

std::ostream& operator<<(std::ostream& out, ns::Data data) {  
  return out << "Some data";
} 

void Use() {
  Dump(ns::Data());
}

同样,LLVM 使用以下消息标记此代码:

my_file2.cpp:5:13: error: call to function 'operator<<' that is neither visible in the template definition nor found by argument-dependent lookup

  std::cout << value << "\n"; 
            ^

my_file2.cpp:17:3: note: in instantiation of function template specialization 'Dump<ns::Data>' requested here

  Dump(ns::Data());
  ^

my_file2.cpp:12:15: note: 'operator<<' should be declared prior to the call site or in namespace 'ns'

  std::ostream& operator<<(std::ostream& out, ns::Data data) {  

和以前一样,非限定查找没有找到任何名称为 operator<< 的声明。 与以前不同,参数类型都包含类类型:

  • 其中一个是类模板类型 std::basic_ostream 的实例
  • 另一种是上例中声明的ns::Data类型

因此,ADL 将在名称空间 std 和 ns 中查找运算符<<。 因为在模板定义期间参数类型之一仍然是依赖的,所以在 Use 期间实例化模板之前不会执行 ADL,这意味着已经声明了它应该找到的 operator<<。 不幸的是,它是在全局命名空间中声明的,而不是在 ADL 将要查找的任何一个命名空间中!

有两种方法可以解决这个问题:

  1. 确保您要调用的函数在可能调用它的模板之前声明。 如果它的参数类型都不包含类,这是唯一的选择。 您可以通过移动模板定义、移动函数定义或在模板之前添加函数的前向声明来完成此操作。
  2. 将该函数移动到与其参数之一相同的命名空间中,以便应用 ADL。

6.2.3 对类模板的依赖基础的非限定查找

某些版本的 GCC 接受以下无效代码:

template <typename T> struct Base {  
  void DoThis(T x) {}  
  static void DoThat(T x) {}
}; 

template <typename T> struct Derived : public Base<T> { 
  void Work(T x) { 
    DoThis(x); // Invalid! 
    DoThat(x); // Invalid!
  }
};

LLVM 正确拒绝此代码并出现以下错误(当 Derived 最终实例化时):

my_file.cpp:8:5: error: use of undeclared identifier ’DoThis’

  DoThis(x);
  ^   
  this-> 

my_file.cpp:2:8: note: must qualify identifier to find this declaration in dependent base class

void DoThis(T x) {}
     ^ 

my_file.cpp:9:5: error: use of undeclared identifier ’DoThat’   
DoThat(x);
^  
this->

my_file.cpp:3:15: note: must qualify identifier to find this declaration in dependent base class  

static void DoThat(T x) {}

如第 6.2.2 节所述,在定义模板 Derived 时查找诸如 DoThis 和 DoThat 之类的非限定名称,而不是在实例化时查找。 在查找类中使用的名称时,我们通常会查看基类。 但是,我们不能查看基类 Base 因为它的类型取决于模板参数 T,所以标准说我们应该忽略它。

正如 LLVM 所指出的,修复是通过在调用前加上 this-> 来告诉编译器我们想要一个类成员:

void Work(T x) {
  this->DoThis(x);
  this->DoThat(x);
 }

或者,您可以告诉编译器确切的位置:

void Work(T x) {
  Base<T>::DoThis(x); 
  Base<T>::DoThat(x);
}

无论方法是否是静态的,这都有效,但要小心:如果 DoThis 是virtual,以这种方式调用它会绕过virtual dispatch!

6.2.4 模板中的不完整类型

以下代码无效,但允许编译器接受:

class IOOptions;  

template <class T> bool read(T &value) {
  IOOptions opts;
  return read(opts, value);
}

class IOOptions { bool ForceReads; };
bool read(const IOOptions &opts, int &x);
template bool read<>(int &);

该标准规定,在定义模板时,不依赖模板参数的类型必须是完整的,如果它们影响程序的行为。 但是,该标准还表示编译器可以自由地不强制执行此规则。 大多数编译器在某种程度上强制执行它; 例如,在上面的代码中写入 opts.ForceReads 在 GCC 中会出错。 在 LLVM 中,一致地强制执行规则的决定提供了更好的体验,但不幸的是,它也会导致一些代码被其他编译器接受而被拒绝。

6.2.5 没有有效实例化的模板

下面的代码包含一个错字:程序员的意思是 init() 而是写了 innit() 。

template <class T> class Processor {
  ...   
  void init();
  ...
};
... 
template <class T> void process() {
  Processor<T> processor;
  processor.innit();      // <-- should be ’init()’ 
  ...
}

不幸的是,编译器无法在检测到此错误后立即对其进行标记:在模板中,我们不允许对诸如 Processor 之类的“依赖类型”做出假设。 假设稍后在此文件中程序员添加了 Processor 的显式特化,如下所示:

template <> class Processor<char*> {
  void innit();
};

现在程序可以运行了——但前提是程序员用 T = char* 实例化了 process()! 这就是为什么在实例化之前诊断模板定义中的错误是困难的,有时甚至是不可能的。

该标准指出,没有有效实例的模板是格式错误的。 LLVM 尝试在定义时而不是实例化时进行尽可能多的检查:这不仅会产生更清晰的诊断,而且在使用预编译头时还大大缩短了编译时间。 这种理念的缺点是 LLVM 有时无法处理文件,因为它们包含不再使用的损坏模板。 解决方法很简单:由于代码未使用,只需将其删除即可。

6.2.6 类类型const变量的默认初始化

类类型的 const 变量的默认初始化需要用户定义的默认构造函数。

如果类或结构没有用户定义的默认构造函数,C++ 不允许您默认构造它的 const 实例。 例如:

class Foo {
public:
  // The compiler-supplied default constructor works fine, so we  

  // don’t bother with defining one.
  ...
}
void Bar() {
  const Foo foo; // Error!
  ...
}

要解决此问题,您可以为该类定义一个默认构造函数:

class Foo { 
public:
  Foo() {}
  ...
};

void Bar() {
  const Foo foo; // Now the compiler is happy.
  ...
}

6.2.7 参数名称查找

由于其实现中的错误,GCC 允许在 C++ 代码中的函数原型中重新声明函数参数名称,例如:

void f(int a, int a);

LLVM 诊断此错误(参数名称已重新声明)。 要解决此问题,请重命名其中一个参数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值