有没有办法同时将一个类型分配给C++中的多个模板?

Is there a way to simultaneously assign a type to multiple templates in C++?

本文关键字:C++ 分配 类型 一个 有没有      更新时间:2023-10-16

这个问题基于下面的示例代码,该代码的灵感来自Sean Parent的演讲。 以下代码的目标是提供一个类似于 boost::any 的对象包装器。我写这段代码是为了教育自己类型擦除。因此,此代码没有实际用途(考虑到已经有 boost::any(。

class ObjWrap {
public:
template <typename T>
ObjWrap(T O) : Self(new Obj<T>(std::move(O))) {}
template <typename T>
friend typename T * getObjPtr(ObjWrap O) {
return static_cast<T*>(O.Self->getObjPtr_());
}
private:
struct Concept {
virtual ~Concept() = 0;
virtual void* getObjPtr_() = 0;
};
template <typename T>
struct Obj : Concept {
Obj(T O) : Data(std::move(O)) {}
void* getObjPtr_() { return static_cast<void*>(&Data); }
T Data;
};
std::unique_ptr<Concept> Self;
};

在我真正提出我的问题之前,让我们从以下几个方面检查代码:

  1. Concept::getObjPtr_返回void*,因为 a(Concept不能成为模板,否则unique_ptr<Concept> Self不起作用; b(void*是我知道如何在C++中以与类型无关的方式返回Obj::Data的唯一方法。如果这是错误的,请纠正我...

  2. T * getObjPtr(ObjWrap O)是一个需要独立于ObjWrap构造函数进行实例化的模板。

  3. ObjWrap的使用基本上包括:a(对现有对象进行新ObjWrap;b(在给定ObjWrap的情况下检索底层对象。例如:

    ObjWrap a(1);
    ObjWrap b(std::string("b"));
    int* p_a = getObjPtr<int>(a);
    std::string* p_b = getObjPtr<std::string>(b);
    

这有效,但很明显getObjPtr<int>(b)没有按预期工作。

所以,我的问题是:

有没有办法修复上面的代码,以便我们可以简单地使用int* p_a = getObjPtr(a)std::string* p_b = getObjPtr(b)或更好的auto p_a = getObjPtr(a)auto p_b = getObjPtr(b)?换句话说,有没有办法C++同时实例化两个模板(如果是这样,我们可以在编译ObjWrap对象时实例化ObjWrap构造函数和T* getObjPtr(ObjWrap),例如ObjWrap a(1)(?

编辑 1:

使 ObjWrap 成为模板化类无济于事,因为它违背了类型擦除的目的。

template <typename T>
class ObjWrap {
/* ... */
};
ObjWrap<int> a(1); // this is no good for type erasure. 

编辑 2:

我正在阅读代码并意识到可以对其进行修改以更好地反映该想法。因此,请同时查看以下代码:

class ObjWrap {
public:
template <typename T>
ObjWrap(T O) : Self(new Obj<T>(std::move(O))) {}
template <typename T>
T * getObjPtr() {
return static_cast<T*>(Self->getObjPtr_());
}
private:
struct Concept {
virtual ~Concept() = 0;
virtual void* getObjPtr_() = 0;
};
template <typename T>
struct Obj : Concept {
Obj(T O) : Data(std::move(O)) {}
void* getObjPtr_() { return static_cast<void*>(&Data); }
T Data;
};
std::unique_ptr<Concept> Self;
};
int main() {
ObjWrap a(1);
ObjWrap b(std::string("b"));
int* p_a = a.getObjPtr<int>();
std::string* p_b = b.getObjPtr<std::string>();
std::cout << *p_a << " " << *p_b << "n";
return 0;
}

此版本的代码与上述代码之间的主要区别在于,T * getObjPtr()是由ObjWrap对象封装的成员函数。

编辑3:

我关于类型擦除的问题得到了接受的答案的回答。但是,关于同时类型实例化到多个模板的问题尚未得到解答。我的猜测是目前C++不允许这样做,但很高兴听到有更多经验的人的意见。

有几件事可能会有所帮助。

首先要说的是,如果 Obj 需要公开对象的地址,它不是 Sean Parent 的"继承是万恶之源"的类型擦除容器。

诀窍是确保 Obj 的接口提供包装器需要的所有语义操作和查询。

为了提供这一点,在概念中缓存对象的地址及其type_id通常是一个合理的想法。

请考虑以下更新的示例,其中有一个公共方法 - operator==。规则是,如果两个 Obj 包含相同类型的对象并且这些对象比较相等,则它们相等。

请注意,地址和type_id:

1( 是实现细节,未在 Obj 的界面上公开

2( 无需虚拟调用即可访问,这会短路不相等的情况。

#include <memory>
#include <utility>
#include <typeinfo>
#include <utility>
#include <cassert>
#include <iostream>
class ObjWrap 
{
public:
template <typename T>
ObjWrap(T O) : Self(new Model<T>(std::move(O))) {}
// objects are equal if they contain the same type of model
// and the models compare equal
bool operator==(ObjWrap const& other) const
{
// note the short-circuit when the types are not the same
// this means is_equal can guarantee that the address can be cast
// without a further check
return Self->info == other.Self->info
&& Self->is_equal(other.Self->addr);
}
bool operator!=(ObjWrap const& other) const
{
return !(*this == other);
}
friend std::ostream& operator<<(std::ostream& os, ObjWrap const& o)
{
return o.Self->emit(os);
}
private:
struct Concept 
{
// cache the address and type here in the concept.
void* addr;
std::type_info const& info;
Concept(void* address, std::type_info const& info)
: addr(address)
, info(info)
{}
virtual ~Concept() = default;
// this is the concept's interface    
virtual bool is_equal(void const* other_address) const = 0;
virtual std::ostream& emit(std::ostream& os) const = 0;
};
template <typename T>
struct Model : Concept 
{
Model(T O) 
: Concept(std::addressof(Data), typeid(T))
, Data(std::move(O)) {}
// no need to check the pointer before casting it.
// Obj takes care of that
/// @pre other_address is a valid pointer to a T    
bool is_equal(void const* other_address) const override
{
return Data == *(static_cast<T const*>(other_address));
}
std::ostream& emit(std::ostream& os) const override
{
return os << Data;
}
T Data;
};

std::unique_ptr<Concept> Self;
};

int main()
{
auto x = ObjWrap(std::string("foo"));
auto y = ObjWrap(std::string("foo"));
auto z = ObjWrap(int(2));
assert(x == y);
assert(y != z);
std::cout << x << " " << y << " " << z << std::endl;
}

http://coliru.stacked-crooked.com/a/dcece2a824a42948

(
  1. 等等(如果这是错误的,请纠正我...

你的前提至少在原则上是错误的,如果不是在实践中也是错误的。您坚持要使getObjPtr()成为虚拟方法,并使用抽象基类。但是 - 你还没有确定这是必要的。请记住 - 使用虚拟方法很昂贵!为什么我应该为虚拟付费才能获得类型擦除?

有没有办法修复上面的代码,以便我们可以简单地使用int* p_a = getObjPtr(a)

把肖恩·帕伦特的演讲标题放在心上(而不是他在演讲中使用继承的事实(,放弃继承,答案应该是肯定的。编辑:擦除类型的代码和取消擦除类型的代码知道类型是什么就足够了 - 只要您不需要以特定于类型的方式对类型擦除的数据进行操作。在肖恩·帕伦特的演讲中,你需要能够制作它的重要副本,移动它,绘制它等。对于std::any/boost::any您可能需要复制和移动,这可能需要虚拟 - 但这是最普遍的用例。

即使std::any也限制了你能做什么和不能做什么,正如这个问题中所讨论的:

为什么 std::any_cast 不支持隐式转换?