C++ 中的模板化成员变量

Templated member variables in C++

经常在编写模板代码时,我发现自己需要将模板类型的实例存储在成员变量中。例如,我可能需要缓存一个值以备后用。我希望能够将我的代码编写为:

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>;
    }
};

成员在使用时是专门的。我的问题:

  1. 当前的 C++ 生成编码工具是否可以使用这种模板化成员变量?
  2. 如果没有,有没有关于这种语言功能的建议?
  3. 如果不是,是否有任何技术原因导致这种事情不可能发生?

很明显,我不想列出所有可能的类型(例如在 std::variant 中),因为这不是生成式编程,如果库的用户不同,则不可能作为作者。

编辑:我认为这在某种程度上回答了我上面的第三个问题。原因是今天的编译器无法将对象的实例化推迟到整个程序被解析之后:

通过结合现有设施,这在图书馆中是可能的。

最简单的实现是

std::unordered_map<std::type_index, std::any>

这有点低效,因为它将每个 std::type_index 对象存储两次(一次在键中,一次在每个 std::any 中),因此具有自定义透明哈希和比较器的 std::unordered_set<std::any> 会更有效率;不过这样会更麻烦。

Example.

正如你所说,图书馆的使用者可能与作者不同;特别是,Foo 析构函数 不知道设置了哪些类型,但它必须找到那些对象并调用它们的析构函数,注意使用的类型集可能是 Foo 的实例之间存在差异,因此此信息必须存储在 Foo.

内的运行时容器中

如果您担心 std::type_indexstd::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_assignfind. (同样,我们遇到了同样的 DRY 违规/低效率,因为删除器可以用作映射中的键;利用它留作 reader 的练习。)

Is such templated member variable possible with current C++ generative coding facilities?

不,不完全是你描述的那样。可能的方法是使封闭的 class 成为模板并使用模板参数来描述 class' 成员的类型。

template< typename T >
struct Foo
{
    T member;
    
    void setMember(T value)
    {
        member = value;
    }
    
    T getMember()
    {
        return member;
    }
};

在 C++14 及更高版本中,有 variable templates,但不能使 class 的模板非静态数据成员。

If not, are there any proposals for such a language feature?

据我所知没有。

If not, are there any technical reasons why such a thing is not possible?

主要原因是这样就无法定义 class 的二进制表示。与模板相反,class 是一种类型,这意味着它的表示必须是固定的,这意味着在程序的任何地方 FooFoo::member 必须表示相同的东西 - 相同的类型,相同的对象大小和二进制布局,等等。另一方面,模板不是类型(或者,在变量模板的情况下,不是对象)。当它被实例化时,它就变成了一个,并且每个模板实例化都是一个单独的类型(在变量模板的情况下 - 对象)。