很遗憾,我无法使用与问题中相同的语法来回答您的问题。因为正如其他人所说auto 与您的假设不同。 auto 只是一个推导类型。
如果分配了int,则auto 的类型为int。但是,这仅适用于推断 auto 类型的情况。任何正在进行的分配只是分配给int,而不是分配给auto。 auto 的类型不是动态的,它的存储也不是动态的,这就是为什么 auto 不能用于在 std::vector 中存储各种不同的类型。
只是添加到另一个答案,希望有助于理解:
auto i = 10;
这里i 的类型是int 而不是auto。
auto b = true;
这里i 的类型是bool 而不是auto。
但是,我可以尽力解决我认为是您面临的问题。
这个答案的作用:
在编译时确保通过具有正确参数类型的函数完成对变量的访问(绕过检查类型的需要)。
提供对已擦除数据的无例外访问(我认为它是安全的...)。
允许修改数据。
这不能做什么:
- 由于重新解释大小写,在编译时运行。
- 允许直接通过 std::vector 中的成员进行赋值,尽管您可以从调用的访问函数中进行赋值。
工作原理:
类型化参数为 T& 的回调函数被类型擦除并存储为泛型函数。该函数的存储是 void (*)() 因为函数指针与普通的 void * 指针不同,它们通常具有不同的大小。
带有类型化参数的访问器函数被设置为由带有两个类型擦除指针参数的函数调用。参数在此函数中被转换为它们的真实类型,这些类型是已知的,因为它们存在于 base 对象的构造函数中。 runner 函数指针中存储了一个指向在构造函数中创建为 lambda 的函数的指针。
access函数运行时,runner函数带有参数data和accessor函数。一旦运行器函数被执行,它会在内部执行带有参数 data 的 accessor 函数,但这一次是在它被转换为正确的类型之后。
当需要访问时,调用上述函数的类型擦除版本,该函数在内部调用类型化函数。我可以在以后的版本中添加对 lambdas 的支持,但它已经相当复杂了,我想我现在就发布......
在基类中存在一个析构函数类。这是存储类型擦除析构函数的一般方式,与Herb Sutters method 几乎相同。这只是确保提供给基础的数据能够运行其析构函数。
基于堆的方法在概念上更简单,您可以在此处运行它:
https://godbolt.org/z/cb-a6m
基于堆栈的方法可能更快,但有更多限制:
https://godbolt.org/z/vxS4tJ
基于代码堆的(更简单的)代码:
#include <iostream>
#include <memory>
#include <utility>
#include <vector>
template <typename T>
struct mirror { using type = T; };
template <typename T>
using mirror_t = typename mirror<T>::type;
struct destructor
{
const void* p = nullptr;
void(*destroy)(const void*) = nullptr;
//
template <typename T>
destructor(T& data) noexcept :
p{ std::addressof(data) },
destroy{ [](const void* v) { static_cast<T const*>(v)->~T(); } }
{}
destructor(destructor&& d) noexcept
{
p = d.p;
destroy = d.destroy;
d.p = nullptr;
d.destroy = nullptr;
}
destructor& operator=(destructor&& d) noexcept
{
p = d.p;
destroy = d.destroy;
d.p = nullptr;
d.destroy = nullptr;
return *this;
}
//
destructor() = default;
~destructor()
{
if (p and destroy) destroy(p);
}
};
struct base
{
using void_ptr_t = void*; // Correct size for a data pointer.
using void_func_ptr_t = void(*)(); // Correct size for a function pointer.
using callback_t = void (*)(void_func_ptr_t, void_ptr_t);
//
void_ptr_t data;
void_func_ptr_t function;
callback_t runner;
destructor destruct;
//
template <typename T>
constexpr base(T * value, void (*callback)(mirror_t<T>&)) noexcept :
data{ static_cast<void_ptr_t>(value) },
function{ reinterpret_cast<void_func_ptr_t>(callback) },
runner{
[](void_func_ptr_t f, void_ptr_t p) noexcept
{
using param = T&;
using f_ptr = void (*)(param);
reinterpret_cast<f_ptr>(f)(*static_cast<T*>(p));
}
},
destruct{ *value }
{}
//
constexpr void access() const noexcept
{
if (function and data) runner(function, data);
}
};
struct custom_type
{
custom_type()
{
std::cout << __func__ << "\n";
}
custom_type(custom_type const&)
{
std::cout << __func__ << "\n";
}
custom_type(custom_type &&)
{
std::cout << __func__ << "\n";
}
~custom_type()
{
std::cout << __func__ << "\n";
}
};
//
void int_access(int & a)
{
std::cout << "int_access a = " << a << "\n";
a = 11;
}
void string_access(std::string & a)
{
std::cout << "string_access a = " << a << "\n";
a = "I'm no longer a large string";
}
void custom_access(custom_type& a)
{
}
int main()
{
std::vector<base> items;
items.emplace_back(new std::string{ "hello this is a long string which doesn't just sit in small string optimisations, this needs to be tested in a tight loop to confirm no memory leaks are occuring." }, &string_access);
items.emplace_back(new custom_type{}, &custom_access);
items.emplace_back(new int (10), &int_access);
//
for (auto& item : items)
{
item.access();
}
for (auto& item : items)
{
item.access();
}
//
return 0;
}