c – 处理模板生成和静态constexpr成员时clang和gcc的不同行为?
考虑以下程序(抱歉长度;这是我能想到的表达问题的最短路径):
#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传送以便于阅读): 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标准?如何以编译器无关的方式创建此效果(最好没有任何运行时开销)? 解决方法
首先,一些解决方案.对于它们两者而言,关键部分是从保证实例化的代码中获取注册服务器的地址.这确保了静态成员的定义也被实例化,从而触发副作用.
第一个依赖于以下事实:Foo的每个特化的默认构造函数的定义被实例化以处理main中a,b和c的默认初始化: template<typename T> class Foo { public: Foo() { (void)&RegistrarWrapper<Foo<T>>::registrar; } }; 缺点是这引入了一个非平凡的构造函数.避免此问题的替代方法如下: 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>>()]; }; 这里的关键是在静态成员的声明中触发实例化,而不依赖于任何初始化器(见下文). 两种解决方案在Clang 3.7.0,GCC 5.2.0和Visual C 2015中均可正常运行,无论是否启用了优化.第二个使用constexpr函数的扩展规则,这是C 14特性.当然,如果需要,有几种简单的方法可以使C 11兼容. 我认为你的解决方案存在的问题是,如果__reg_ptr的初始化程序的值没有在某处使用,则无法保证它的实例化. N4527的一些标准报价: 14.7.1p2:
这并没有完全解决constexpr案例,因为(我认为)它正在讨论静态数据成员的外部定义,这种成员使用得很多(它与注册商更相关),但它很接近. 14.7.1p1:
这保证了第二种解决方案的有效性.请注意,它不保证静态数据成员的类内初始化程序. 关于constexpr结构的实例化似乎存在一些不确定性.有一个CWG 1581,与我们的案例无关,除了在最后,它讨论了在常量表达式评估期间或解析期间是否发生constexpr实例化这一事实尚不清楚.这方面的一些澄清也可能为您的解决方案提供一些保证(无论哪种方式……),但我们必须等待. 第三种变体:使解决方案工作的一种方法是显式实例化Foo的特化,而不是依赖于隐式实例化: 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]),因此需要格外小心.我认为前两个解决方案更可靠. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |