jjc*_*mar 5 c++ templates tuples
假设我有一个如下所示的模板
template <typename T>
struct A {};
Run Code Online (Sandbox Code Playgroud)
在另一个类中存储该模板的不同实例化向量的最佳方法是什么?
我考虑过类似的事情,但是当然,它迫使我声明所有我不想支持的类型,在我的特殊情况下,这可能会迅速升级。
template <typename ...Args>
struct Holder
{
std::tuple<std::vector<Args>...> tup;
template <typename T>
void push_back(T &&t); // Method to insert in the appropiate vector, considering only one match
}
Run Code Online (Sandbox Code Playgroud)
鉴于我有 Push_back 方法,我用它来将元素插入元组中的适当向量中,并且除了使用 Push_back 方法插入的类型之外我不需要任何其他类型,是否有更好的方法来定义持有者?
编辑:理想情况下,我希望能够在元组的不同元素上调用函数模板。
通用调用
template <typename T>
void foo(T t)
Run Code Online (Sandbox Code Playgroud)
对 A 的实际实例化的专门调用
template <typename T>
void foo(A<T> t)
Run Code Online (Sandbox Code Playgroud)
听起来您正在寻找的模式称为“类型擦除” ——它允许以某种同类方式收集和访问异构类型的集合。
在评论中,您指出了两个重要组成部分:
draw(A)函数调用。为了执行类型擦除,我们需要有一种方法将对象存储为某种同类类型,并且您将需要某种方法来访问重要的功能。请注意,类型擦除通常是一个单向过程(例如,一旦擦除,很难在不轮询的情况下重新提取原始类型。想想如何std::function操作)。
您可以通过以下几种方法来实现此目的:
std::any(C++17 及更高版本)有点现代的方法是结合使用 C++17 的std::any类型和一种机制来提取原始类型并生成所需的调用。最简单的机制是使用函数指针,它将指向静态函数模板以保留原始类型。考虑以下(已记录的)示例:
template <typename T>
class A { /* ... */ };
auto draw(const A<int>&) -> void;
auto draw(const A<long>&) -> void;
// ...
// Named 'drawable' since you indicated 'draw(A)' calls
class Drawable
{
private:
// A handler function to perform the operation.
// Each pointer will "know" the original T type
using handler_fn_t = auto (*)(const std::any&) -> void;
std::any m_storage;
handler_fn_t m_handler;
// The handler function. This knows the original type bound
// to the std::any for an easy cast back, and then calls the
// desired function.
template <typename T>
static auto type_stub(const std::any& storage) -> void {
const auto* p = std::any_cast<T>(&storage);
// Call the free-function named 'draw'
draw(*p);
}
public:
// A constructor that just constructs the object
// This uses SFINAE to constrain to functions where 'draw(...)' is
// well-formed. If you are using C++20, this can be a "requires" clause.
template <typename T, typename = decltype(draw(std::declval<const T&>()))>
Drawable(const T& t)
: m_storage{t}, m_handler{type_stub<T>}
{
}
Drawable(const Drawable&) = default;
// The draw function. This just calls the underlying handler
auto draw() const -> void {
// Call the function stub
(*m_handler)(m_storage);
}
};
Run Code Online (Sandbox Code Playgroud)
上面假设仅使用 1 个单个函数,具有一个特定的可变性 ( const)。如果需要更多功能,可以修改函数指针技术以提供“有效负载”参数和“操作”枚举来区分该值。any“有效负载”为给定操作提供正确的 CV 限定,这可以节省const_cast:
class Drawable
{
private:
enum class operation {
draw,
get_something,
set_something,
};
// Modified to take an "operation" and "void*" param
using handler_fn_t = auto (*)(operation, void*) -> void;
struct draw_payload {
const std::any& storage;
};
struct get_payload {
const std::any& storage;
int some_value_to_return;
};
struct set_payload {
std::any& storage;
int some_value_to_set;
};
std::any m_storage;
handler_fn_t m_handler;
// Modified to take the operation and the payload
template <typename T>
static auto type_stub(operation op,
void* payload) -> void
{
const auto* p = std::any_cast<T>(&storage);
switch (op) {
case operation::draw: {
auto* payload_type = static_cast<draw_payload*>(payload);
draw(*std::any_cast<T>(&payload_type->storage));
break;
}
case operation::get_something: {
auto* payload_type = static_cast<get_payload*>(payload);
out_payload->some_value_to_return = p->get_value();
break;
}
case operation::set_something: {
auto* payload_type = static_cast<set_payload*>(payload);
p->set_value(in_payload->some_value_to_set);
break;
}
}
}
public:
...
// The draw function. This just calls the underlying handler
auto draw() const -> void {
auto payload = draw_payload{m_storage};
// Call the function stub
(*m_handler)(operation::draw, &payload);
}
auto get() const -> int {
auto payload = get_payload{m_storage};
(*m_handler)(operation::get, &payload);
return payload.some_value_to_return;
}
auto set(int value) -> void {
auto payload = set_payload{m_storage, value};
(*m_handler)(operation::set, &payload);
}
};
Run Code Online (Sandbox Code Playgroud)
总的来说,这是一项非常强大的技术。
另一种选择编写起来更自然,但利用了虚拟层次结构和堆内存unique_ptr。
这个想法只是在类中获取一个T类型,并生成一个所需的接口,以及一个将该类型包装T成满足该接口的实现。
使用上面的第二个例子,也可以写成:
class Drawable
{
private:
// The interface we want each 'T' to subscribe to
struct Interface {
virtual ~Interface() = default;
virtual auto draw() const -> void = 0;
virtual auto get() const -> int = 0;
virtual auto set(int) -> void = 0;
};
// An implementation that satisfies the interface, in terms of T
template <typename T>
struct Concrete : Interface {
explicit Concrete(const T& v) : value{v}{}
T value;
auto draw() const -> void override { return ::draw(value); }
auto get() const -> int override { return value.get_value(); }
auto set(int v) -> void override { value.set_value(v); }
};
// Something to hold onto the T type.
// This doesn't necessarily have to be a unique_ptr; just something
// that holds onto the interface in some way.
std::unique_ptr<Interface> m_interface;
public:
// Similar constructor to before; we create a concrete pointer, and let
// it erase into the Interface
template <typename T, typename = decltype(draw(std::declval<const T&>()))>
Drawable(const T& t)
: m_interface{std::make_unique<Concrete<T>>(t)}
{
}
Drawable(Drawable&&) = default;
// Just call the basic underlying types
auto draw() const -> void {
m_interface->draw();
}
auto get() const -> int {
return m_interface->get();
}
auto set(int v) -> void {
m_interface->set(v);
}
};
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,这种方法写起来更“自然”——但通常也需要某种形式的分配内存,这可能会导致碎片,并且可能是不可取的。这也可以通过固定大小的缓冲区和指针来完成,但这也会导致一些冗余。
这些只是解决所描述问题的一些可能方法
| 归档时间: |
|
| 查看次数: |
96 次 |
| 最近记录: |