首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >clang和gcc在处理模板生成和静态成员时的不同行为?

clang和gcc在处理模板生成和静态成员时的不同行为?
EN

Stack Overflow用户
提问于 2015-11-03 16:46:38
回答 1查看 247关注 0票数 4

请考虑以下程序(对于长度表示抱歉;这是我能想到的表达问题的最短方式):

代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <typeindex>

using namespace std;

std::vector<std::type_index>&
test_vector()
{
  static std::vector<std::type_index> rv;
  return rv;
}

template <typename T>
class RegistrarWrapper;

template<typename T>
class Registrar
{
  Registrar()
  {
    auto& test_vect = test_vector();
    test_vect.push_back(std::type_index(typeid(T)));
  }
  friend class RegistrarWrapper<T>;
};

template <typename T>
class RegistrarWrapper
{
  public:
    static Registrar<T> registrar;
    typedef Registrar<T> registrar_t;
};

template <typename T>
Registrar<T> RegistrarWrapper<T>::registrar;


template <typename T>
class Foo
{
  public:
    // Refer to the static registrar somewhere to make the compiler
    // generate it ?!?!?!?
    static constexpr typename RegistrarWrapper<Foo<T>>::registrar_t& __reg_ptr =
      RegistrarWrapper<Foo<T>>::registrar;
};


int main(int argc, char** argv)
{
  Foo<int> a;
  Foo<bool> b;
  Foo<std::string> c;

  for(auto&& data : test_vector()) {
    std::cout << data.name() << std::endl;
  }

}

当用clang++编译(版本3.5.2,当然是用-std=c++11编译)时,这个程序输出(通过管道通过c++filt实现可读性):

代码语言:javascript
复制
Foo<int>
Foo<bool>
Foo<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >

但是对于g++ (尝试过的4.8.5、4.9.3和5.2.0版本),它什么也不输出!这里发生了什么事?哪个编译器符合c++标准?如何以编译器无关的方式创建这种效果(最好没有任何运行时开销)?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2015-11-03 23:33:12

首先,有几个解决方案。对于这两个人来说,重要的部分是从保证实例化的代码中获取registrar的地址。这确保静态成员的定义也被实例化,从而触发副作用。

第一种方法依赖于以下事实:实例化Foo的每个专门化的默认构造函数的定义,以处理main中的abc的默认初始化。

代码语言:javascript
复制
template<typename T> class Foo
{
public:
   Foo() { (void)&RegistrarWrapper<Foo<T>>::registrar; }
};

一个缺点是这引入了一个非平凡的构造函数。避免这一问题的另一种办法是:

代码语言:javascript
复制
template<class T> constexpr std::size_t register_class() 
{ 
   (void)&RegistrarWrapper<T>::registrar; 
   return 1; 
}

template<typename T> class Foo
{
   static char reg[register_class<Foo<T>>()];
};

这里的关键是触发静态成员声明中的实例化,而不依赖于任何初始化器(参见下面)。

这两种解决方案在Clang3.7.0、GCC 5.2.0和Visual C++ 2015中都能很好地工作,无论是否启用了优化。第二种方法是对constexpr函数使用扩展规则,这是一个C++14特性。当然,如果需要的话,有几种简单的方法可以使它与C++11兼容。

我认为您的解决方案的问题是,如果__reg_ptr的值没有在某个地方使用,就不能保证它的初始化程序会被实例化。一些来自N4527的标准引号:

14.7.1p2:

..。除非静态数据成员本身的使用方式要求静态数据成员的定义才能存在,否则不会发生静态数据成员的初始化(以及任何相关的副作用)。

这并不能完全解决constexpr的问题,因为(我认为)它是在讨论使用odr的静态数据成员的类外定义(它与registrar更相关),但它非常接近。

14.7.1p1:

..。类模板专门化的隐式实例化导致声明的隐式实例化,而不是类成员函数、成员类、作用域成员枚举、静态数据成员和成员模板的定义、默认参数或异常规范的隐式实例化.

这保证了第二个解决方案的工作。请注意,它并不保证任何关于静态数据成员的类内初始化器的内容。

constexpr结构的实例化方面似乎存在一些不确定性。这里有CWG 1581,它与我们的情况不太相关,只是在最后,它谈到了一个事实,即constexpr实例化是在常量表达式计算期间还是在解析过程中发生的还不清楚。这方面的一些澄清也可能为您的解决方案提供一些保证(无论哪种方式.)但我们得等着。

第三个变体:使您的解决方案工作的方法是显式实例化Foo的专门化,而不是依赖于隐式实例化:

代码语言:javascript
复制
template class Foo<int>;
template class Foo<bool>;
template class Foo<std::string>;

int main()
{
   for(auto&& data : test_vector()) {
      std::cout << data.name() << std::endl;
   }
}

这也适用于所有三个编译器,并依赖于14.7.2p8:

命名类模板专门化的显式实例化也是其每个成员的同一类型(声明或定义)的显式实例化。

考虑到这些都是显式实例化定义,这似乎足以说服GCC实例化__reg_ptr的初始化器。但是,这些显式实例化定义只能在整个程序中出现一次(14.7p5.1),因此需要格外小心。我认为前两种解决方案更可靠。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/33504651

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档