问题描述
经常在编写模板化代码时,我发现自己需要在成员变量中存储模板类型的实例。例如,我可能需要缓存稍后使用的值。我希望能够将我的代码编写为:
struct Foo
{
template<typename T>
T member;
template<typename T>
void setMember(T value)
{
member<T> = value;
}
template<typename T>
T getMember()
{
return member<T>;
}
};
成员在使用时专门化的地方。我的问题:
很明显,我不想列出所有可能的类型(例如在 std::variant
中),因为这不是生成式编程,如果库的用户与作者。
编辑:我认为这在一定程度上回答了我上面的第三个问题。原因是今天的编译器无法将对象的实例化推迟到整个程序被解析之后: https://stackoverflow.com/a/27709454/3847255
解决方法
通过结合现有设施,这可以在图书馆中实现。
最简单的实现是
std::unordered_map<std::type_index,std::any>
这有点低效,因为它将每个 std::type_index
对象存储两次(一次在键中,一次在每个 std::any
中),因此具有自定义透明哈希和比较器的 std::unordered_set<std::any>
会更多高效的;不过,这将是更多的工作。
如你所说,图书馆的用户可能和作者不一样;特别是,Foo
的析构函数不知道设置了哪些类型,但它必须定位这些对象并调用它们的析构函数,注意所使用的类型集可能不同 在 Foo
的实例之间,因此此信息必须存储在 Foo
内的运行时容器中。
如果您对 std::type_index
和 std::any
隐含的 RTTI 开销持谨慎态度,我们可以将它们替换为较低级别的等效项。对于 std::type_index
,您可以使用指向 static
标签变量模板实例化(或任何类似工具)的指针,对于 std::any
,您可以使用类型擦除的 std::unique_ptr<void,void(*)(void*)>
,其中deleter 是一个函数指针:
using ErasedPtr = std::unique_ptr<void,void(*)(void*)>;
std::unordered_map<void*,ErasedPtr> member;
struct tag {};
template<class T> inline static tag type_tag;
member.insert_or_assign(&type_tag<T>,ErasedPtr{new T(value),[](void* p) {
delete static_cast<T*>(p);
}});
Example。请注意,一旦将 std::unique_ptr
的删除器设为函数指针,它就不再是默认构造的,因此我们不能再使用 operator[]
,而必须使用 insert_or_assign
和 {{1 }}。 (同样,我们遇到了相同的 DRY 违规/低效率,因为删除器可以用作映射的键;利用这一点留给读者作为练习。)
这种模板化的成员变量是否可以使用当前的 C++ 生成编码工具?
不,不是你描述的那样。可能的做法是使封闭类成为模板,并使用模板参数来描述类成员的类型。
template< typename T >
struct Foo
{
T member;
void setMember(T value)
{
member = value;
}
T getMember()
{
return member;
}
};
在 C++14 及更高版本中,有 variable templates,但您不能使模板成为类的非静态数据成员。
如果没有,有没有关于这种语言功能的建议?
我不知道。
如果没有,是否有任何技术原因导致这种事情是不可能的?
主要原因是这将使得无法定义类的二进制表示。与模板相反,类是一种类型,这意味着它的表示必须是固定的,这意味着在程序中的任何地方 Foo
和 Foo::member
必须表示相同的东西 - 相同的类型,相同的对象大小和二进制布局,等等。另一方面,模板不是类型(或者,在变量模板的情况下,不是对象)。它在实例化时成为一个,并且每个模板实例化都是一个单独的类型(在变量模板 - 对象的情况下)。