考虑以下程序(抱歉长度;这是我能想到的表达问题的最短路径):
#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 ++标准?如何以编译器无关的方式创建此效果(最好没有任何运行时开销)?
答案 0 :(得分:1)
首先,一些解决方案。对于它们两者,基本部分是从保证实例化的代码中获取registrar
的地址。这样可以确保静态成员的定义也被实例化,从而触发副作用。
第一个依赖于以下事实:Foo
的每个特化的默认构造函数的定义被实例化以处理a
,b
和{{1}的默认初始化} c
:
main
缺点是这引入了一个非平凡的构造函数。避免此问题的替代方法如下:
template<typename T> class Foo
{
public:
Foo() { (void)&RegistrarWrapper<Foo<T>>::registrar; }
};
这里的关键是触发静态成员声明中的实例化,而不依赖于任何初始化器(见下文)。
两种解决方案在Clang 3.7.0,GCC 5.2.0和Visual C ++ 2015中均可正常运行,无论是否启用了优化。第二个使用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>>()];
};
函数的扩展规则,这是C ++ 14的特性。当然,如果需要,有几种简单的方法可以使C ++ 11兼容。
我认为您的解决方案存在的问题是,如果constexpr
的初始值设定项未在某处使用,则无法保证__reg_ptr
的初始值设定项得到实例化。 N4527的一些标准报价:
14.7.1p2:
[...]静态数据的初始化(以及任何相关的副作用) 除非静态数据成员本身在其中使用,否则不会发生成员 一种需要静态数据成员定义的方法。
这并没有完全解决constexpr
案例,因为(我认为)它正在谈论一个有用的静态数据成员的类外定义(它&& #39;与registrar
更相关,但它已接近。
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]),因此需要格外小心。我认为前两个解决方案更可靠。