К сожалению, я не могу ответить на ваш вопрос с тем же синтаксисом, что и в вопросе. Потому что, как утверждают другие, auto
работает не так, как вы предполагаете. auto
- это просто выведенный тип.
Если ему присваивается int
, тип auto
равен int
. Однако это применимо только тогда, когда определяется тип auto. Любое исходящее назначение - это просто присвоение int
, а не auto
. Тип auto
не является динамическим c, и его хранилище также не является динамическим c, поэтому auto нельзя использовать для хранения различных типов в std::vector
.
Просто для добавления на другой ответ, надеюсь, помогая понять:
auto i = 10;
Тип i
здесь int
, а не auto
.
auto b = true;
Тип i
здесь bool
, а не auto
.
Тем не менее, я могу сделать все возможное, чтобы решить то, что, по моему мнению, является проблемой, с которой вы столкнулись.
Что делает этот ответ:
Во время компиляции убедитесь, что доступ к переменной осуществляется через функцию с правильным типом параметра (минуя необходимость проверки типа).
Предоставьте доступ набирать стертые данные без исключений (я думаю, это безопасно ...).
Разрешить изменение данных.
Чего это не делает:
- Запуск во время компиляции из-за повторной интерпретации регистра.
- Разрешить присваивание напрямую через члены в std :: vector <>, хотя вы можете назначить из вызываемой функции доступа.
Как это работает:
Функция обратного вызова с типизированным параметром T & is type стирается и сохраняется как общая функция c. Хранение для этой функции - void (*) (), потому что указатели на функции не совпадают с обычными указателями void *, они часто имеют разные размеры.
Функция доступа с типизированным параметром настроена для вызова функцией с двумя типами параметров стертого указателя. Параметры преобразуются в их действительные типы в этой функции, типы известны, поскольку они присутствовали в конструкторе объекта base . Указатель на функцию, созданную в конструкторе в виде лямбды, сохраняется в указателе функции runner .
При запуске функции access бегун Функция с параметрами data и функцией acessor . Как только функция бегуна выполняется, она внутренне выполняет функцию accessor с параметром data , но на этот раз после того, как она приведена к правильному типу.
Когда требуется доступ вызывается стертая версия указанной выше функции, которая внутренне вызывает типизированную функцию. Я могу добавить поддержку лямбда-выражений в более поздней версии этого, но это уже довольно сложно, и я подумал, что я просто отправлю сейчас ...
Внутри базового класса существует класс деструктор . Это общий способ хранения уничтоженного типа деструктора, почти такой же, как метод Херба Саттерса . Это просто гарантирует, что данные, передаваемые базе, имеют свой деструктор.
Подход на основе кучи концептуально проще, вы можете запустить его здесь: 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;
}