C++ 在C+;中是否有一种方法可以同时将一个类型分配给多个模板+;?
这个问题基于下面的示例代码,其灵感来自。 下面代码的目标是提供类似于的对象包装器。我写这段代码是为了自学类型擦除。因此,这段代码没有实际用途(考虑到已经有boost::any) 这是可行的,但很明显,C++ 在C+;中是否有一种方法可以同时将一个类型分配给多个模板+;?,c++,templates,generic-programming,type-erasure,C++,Templates,Generic Programming,Type Erasure,这个问题基于下面的示例代码,其灵感来自。 下面代码的目标是提供类似于的对象包装器。我写这段代码是为了自学类型擦除。因此,这段代码没有实际用途(考虑到已经有boost::any) 这是可行的,但很明显,getObjPtr(b)没有按预期工作 所以,我的问题是: 有没有办法修复上面的代码,这样我们就可以简单地使用int*p_a=getObjPtr(a)和std::string*p_b=getObjPtr(b)或者更好的auto p_a=getObjPtr(a)和auto p_b=getObjPtr
getObjPtr(b)
没有按预期工作
所以,我的问题是:
有没有办法修复上面的代码,这样我们就可以简单地使用int*p_a=getObjPtr(a)
和std::string*p_b=getObjPtr(b)
或者更好的auto p_a=getObjPtr(a)
和auto p_b=getObjPtr(b)
?换言之,C++中是否有一种方法同时对两个模板进行实例化(如果是这样的话,我们可以在<代码> Objult< /Cord>对象,例如<代码> ObjRead(1)< /C> > /< P>编译时,实例化<代码> Objult< /Cuff>构造函数和<代码> T*GETObjpTR(ObjRead)< /C>。
编辑1:
使ObjWrap成为模板化类并没有帮助,因为这样做会破坏类型擦除的目的
template <typename T>
class ObjWrap {
/* ... */
};
ObjWrap<int> a(1); // this is no good for type erasure.
模板
类ObjWrap{
/* ... */
};
ObjWrap a(1);//这不利于类型擦除。
编辑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;
}
类ObjWrap{
公众:
模板
ObjWrap(to):Self(新的Obj(std::move(O)){}
模板
T*getObjPtr(){
返回静态_cast(Self->getObjPtr_());
}
私人:
结构概念{
虚~概念()=0;
虚拟void*getObjPtr_u3;=0;
};
模板
结构对象:概念{
Obj(to):数据(std::move(O)){
void*getObjPtr_u2;(){return static_cast(&Data);}
T数据;
};
std::唯一的自身;
};
int main(){
obja(1);
ObjWrap b(标准::字符串(“b”);
int*p_a=a.getObjPtr();
std::string*p_b=b.getObjPtr();
标准::cout
(等)如果这是错误的,请纠正我
你的前提至少在原则上是错误的,如果不是在实践中也是错误的。你坚持让getObjPtr()
成为一个虚拟方法,并使用一个抽象基类。但是-你还没有建立这一点是必要的。记住-使用虚拟方法是昂贵的!我为什么要为获得类型擦除而支付虚拟方法的费用
有没有办法修复上面的代码,这样我们就可以简单地使用int*p_a=getObjPtr(a)
牢记Sean Parent的演讲标题(与他在演讲中使用继承的事实相反),删除继承,答案应该是肯定的。编辑:擦除类型的代码和取消擦除类型的代码就足以知道类型是什么-只要您不需要以特定于类型的方式对类型擦除的数据执行操作。在Sean Parent的谈话中,您需要能够制作它的非平凡副本,不是吗o移动它,绘制它等等。使用std::any
/boost::any
您可能需要复制和移动,这可能需要虚拟化,但这是最常见的用例
即使是std::any
也会限制您可以做和不能做的事情,如本问题所述:
有几件事可能会有所帮助
首先要说的是,如果Obj需要公开对象的地址,那不是Sean Parent的“继承是万恶之源”类型擦除容器
诀窍是确保Obj的接口提供包装器所需的所有语义操作和查询
为了提供这一点,在概念中缓存对象的地址及其type_id
通常是一个合理的想法
考虑以下更新的示例,其中有一个公共方法-operator==。规则是,如果两个对象包含相同类型的对象,并且这些对象比较相等,则它们相等
请注意,地址和类型\u 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;
}
#包括
#包括
#包括
#包括
#包括
#包括
类ObjWrap
{
公众:
模板
ObjWrap(to):Self(新模型(std::move(O)){}
//如果对象包含相同类型的模型,则它们是相等的
//模型之间的比较结果相同
布尔运算符==(ObjWrap常量和其他)常量
{
//注意类型不同时的短路
//这意味着is_equal可以保证地址可以被强制转换
//没有进一步检查
返回Self->info==other.Self->info
&&Self->is_equal(其他.Self->addr);
}
布尔运算符!=(ObjWrap常量和其他)常量
{
返回!(*此==其他);
}
friend std::ostream&Operator但是Sean Parent在演讲中介绍的代码也使用虚拟方法…boost::any也使用虚拟方法…“为什么我应该为获得类型擦除而付费?”如何在没有虚拟函数的情况下实现类型擦除?当然,您可以使用函数指针,但之后您可以有效地重新实现虚拟函数。“使用虚拟方法是昂贵的!”使用虚拟方法是有代价的。是否“昂贵”取决于情况。@πάνταῥεῖ 我正要发布一个回答OP真正的问题的答案。请重新打开。你不应该在重新阅读的基础上向一个问题添加越来越多的代码。这不是该网站应该使用的方式。@einpoklum对此表示抱歉。我想这可能没问题,因为它本质上是相同的代码,在网上看到它们可能会很有趣页面。“首先要说的是,如果Obj需要公开对象的地址,那不是Sean Parent的‘继承是万恶之源’类型擦除容器。”感谢你指出这一点。你是对的。我的代码试图表现得像boost::any,而我是w
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;
}
#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;
}