c++ 是否有等同于 Typescript 的交集类型?
Does c++ have an equivalent to Typescripts' intersection type?
我在使用 Typescript 一段时间后回到了 C++,了解到在 Typescript 中我们可以做这样的事情:
let ojbect: InterfaceA & InterfaceB = new InterfaceAandBImplementation();
它被称为 Intersection Type。我想在 C++ 中做同样的事情,使用纯虚拟 classes 作为接口,但我似乎无法完成同样的事情……这变得很烦人。显然,Java 在泛型中也支持它 - 据我所知 - 所以它不仅仅是 TS。
具体用例
我的 OpenGL 应用程序有 Light
和 Camera
classes,我想将它们的实例附加到世界对象。
class Camera
: public Interface::Positionable
, public Interface::Directionable
{ ... }
class Light
{ ... }
class SpotLight
: public Light
, public Interface::Positionable
, public Interface::Directionable
{ ... }
这些是接口:
namespace Interface
{
class Positionable
{
public:
virtual const glm::vec3& getPosition() const = 0;
virtual void addToPosition(const glm::vec3& position) = 0;
virtual void setPosition(const glm::vec3& position) = 0;
};
class Directionable
{
public:
virtual const glm::vec3& getDirection() const = 0;
virtual void addToDirection(const glm::vec3& direction) = 0;
virtual void setDirection(const glm::vec3& direction) = 0;
};
}
使用 FixedAttachment
class 我想用相机跟踪物体,但也用这个 class 附加聚光灯(例如宇宙飞船的头灯)。
class FixedAttachment
{
public:
FixedAttachment(std::shared_ptr<Interface::Positionable> target, std::shared_ptr<Interface::Directionable> tracker, const glm::vec3& position)
: Attachment{ target },
m_tracker{ tracker },
m_position{ position }
{
}
std::shared_ptr<Interface::Positionable> getSharedTarget() const { return m_target; }
Interface::Positionable* getTarget() const { return m_target.get(); }
const glm::vec3& getPosition() const { return m_position; }
void updateTracker()
{
/* Calculate new direction, position here for the tracker based upon target */
// m_tracker->setDirection(calculatedDirection);
// m_tracker->setPosition(calculatedPosition);
// ^^^^^^^^^ impossible, not a positionable yet
}
private:
std::shared_ptr<Interface::Positionable> m_target;
std::shared_ptr<Interface::Directionable> m_tracker;
glm::vec3 m_position;
};
正如 updateTracker
中的代码注释所指出的,我需要 m_tracker
才能成为 Interface::Positionable
。
所以我真正想要的只是像这样存储我的 m_tracker
:
std::shared_ptr<Interface::Positionable & Interface::Directionable> m_tracker;
(并修改构造函数等)我可以通过创建一个结合其他 2:
的接口 class 来解决这个问题
class PosDirIntersection
: public Interface::Positionable,
: public Interface::Directionable
{}
但这只是避免了这个问题,因为那时我还必须从中派生相机和聚光灯才能通过它。如果我随后创建一个接受类型 LightDirIntersection
(Light & Interface::Directionable
) 变量的函数,我将无法再向它传递类型 SpotLight
的变量。另外,它迫使我修改 classes 只是为了将它传递到他们定义之外的某个地方,这就是我不喜欢这个解决方案的原因。
到目前为止,我还没有找到解决这个问题的办法。有吗?
在 typescript 中,type C = A & B
基本上是一种欺骗你获得 interface C extends A, B {}
的方法。
这样做只是简单地从左到右合并成员。如果这是您想要的,那么 class C : A, B {}
可能是最接近的。
否则,对于函数中的类型要求,您将需要概念(或 SFINAE 等价物,但很快就会变得混乱):
template <class T>
concept Positionable = requires(T obj){
{ std::is_base_of_v<Interface::Positionable, T> }
}
template <class T>
concept Directionable = requires(T obj){
{ std::is_base_of_v<Interface::Directionable, T> }
}
然后您可以对概念进行任意组合并在您的函数中要求它们:
void doStuff(Directionable auto direct){}
void doOtherStuff(Positionable auto direct){}
旁注,您似乎需要的感觉很像 Entity Component System pattern. In which case, EnTT 是一个很好的实现。您也可以编写自己的代码以满足更具体的需求和更小的占用空间。
我在使用 Typescript 一段时间后回到了 C++,了解到在 Typescript 中我们可以做这样的事情:
let ojbect: InterfaceA & InterfaceB = new InterfaceAandBImplementation();
它被称为 Intersection Type。我想在 C++ 中做同样的事情,使用纯虚拟 classes 作为接口,但我似乎无法完成同样的事情……这变得很烦人。显然,Java 在泛型中也支持它 - 据我所知 - 所以它不仅仅是 TS。
具体用例
我的 OpenGL 应用程序有 Light
和 Camera
classes,我想将它们的实例附加到世界对象。
class Camera
: public Interface::Positionable
, public Interface::Directionable
{ ... }
class Light
{ ... }
class SpotLight
: public Light
, public Interface::Positionable
, public Interface::Directionable
{ ... }
这些是接口:
namespace Interface
{
class Positionable
{
public:
virtual const glm::vec3& getPosition() const = 0;
virtual void addToPosition(const glm::vec3& position) = 0;
virtual void setPosition(const glm::vec3& position) = 0;
};
class Directionable
{
public:
virtual const glm::vec3& getDirection() const = 0;
virtual void addToDirection(const glm::vec3& direction) = 0;
virtual void setDirection(const glm::vec3& direction) = 0;
};
}
使用 FixedAttachment
class 我想用相机跟踪物体,但也用这个 class 附加聚光灯(例如宇宙飞船的头灯)。
class FixedAttachment
{
public:
FixedAttachment(std::shared_ptr<Interface::Positionable> target, std::shared_ptr<Interface::Directionable> tracker, const glm::vec3& position)
: Attachment{ target },
m_tracker{ tracker },
m_position{ position }
{
}
std::shared_ptr<Interface::Positionable> getSharedTarget() const { return m_target; }
Interface::Positionable* getTarget() const { return m_target.get(); }
const glm::vec3& getPosition() const { return m_position; }
void updateTracker()
{
/* Calculate new direction, position here for the tracker based upon target */
// m_tracker->setDirection(calculatedDirection);
// m_tracker->setPosition(calculatedPosition);
// ^^^^^^^^^ impossible, not a positionable yet
}
private:
std::shared_ptr<Interface::Positionable> m_target;
std::shared_ptr<Interface::Directionable> m_tracker;
glm::vec3 m_position;
};
正如 updateTracker
中的代码注释所指出的,我需要 m_tracker
才能成为 Interface::Positionable
。
所以我真正想要的只是像这样存储我的 m_tracker
:
std::shared_ptr<Interface::Positionable & Interface::Directionable> m_tracker;
(并修改构造函数等)我可以通过创建一个结合其他 2:
的接口 class 来解决这个问题class PosDirIntersection
: public Interface::Positionable,
: public Interface::Directionable
{}
但这只是避免了这个问题,因为那时我还必须从中派生相机和聚光灯才能通过它。如果我随后创建一个接受类型 LightDirIntersection
(Light & Interface::Directionable
) 变量的函数,我将无法再向它传递类型 SpotLight
的变量。另外,它迫使我修改 classes 只是为了将它传递到他们定义之外的某个地方,这就是我不喜欢这个解决方案的原因。
到目前为止,我还没有找到解决这个问题的办法。有吗?
在 typescript 中,type C = A & B
基本上是一种欺骗你获得 interface C extends A, B {}
的方法。
这样做只是简单地从左到右合并成员。如果这是您想要的,那么 class C : A, B {}
可能是最接近的。
否则,对于函数中的类型要求,您将需要概念(或 SFINAE 等价物,但很快就会变得混乱):
template <class T>
concept Positionable = requires(T obj){
{ std::is_base_of_v<Interface::Positionable, T> }
}
template <class T>
concept Directionable = requires(T obj){
{ std::is_base_of_v<Interface::Directionable, T> }
}
然后您可以对概念进行任意组合并在您的函数中要求它们:
void doStuff(Directionable auto direct){}
void doOtherStuff(Positionable auto direct){}
旁注,您似乎需要的感觉很像 Entity Component System pattern. In which case, EnTT 是一个很好的实现。您也可以编写自己的代码以满足更具体的需求和更小的占用空间。