Предположим, что у меня есть такой шаблонный класс:
template<typename Fn>
struct S {
S(Fn fn) : fn_(fn) {}
const auto& fn() const {
return fn_;
}
Fn fn_;
}
Бывают случаи, когда вам нужно передать свой объект как void*, поэтому, если у меня есть такой указатель:
void* ptr = new S{[]() { std::cout << "hi!\n";}};
Все в порядке. Но проблема в том, что если я попытаюсь привести ptr обратно в свой класс, чтобы получить доступ к fn() методу, мне понадобится информация о типе. Поэтому следующий код не скомпилируется:
auto s = static_cast<S*>(ptr);
Теперь мой вопрос заключается в том, существует ли шаблон или метод для переноса информации о типе вместе с данными, чтобы сделать это приведение возможным или нет? Использование любого подхода/шаблона, который возвращает правильный экземпляр, в порядке. Варианты использования этой проблемы — когда вы работаете с библиотеками, которые передают контекст между методами как void*. Если вы хотите использовать шаблонный класс в качестве этого контекста, возникает эта проблема.
Есть идеи?
Я вижу несколько вариантов:
Наследуйте каждый класс от базового класса и используйте динамический полиморфизм.
Используйте стандартную::функцию
Используйте std::variant
Используйте стандартный::любой
Каждый из этих методов отличается по стоимости и функциям, но будет делать то, что вы хотели.
На самом деле я знал все эти методы, и ни один из них не работал у меня. мне нужна была концепция type easure, упомянутая в комментарии. Во всем, что вы предложили, мне нужно знать тип в конце концов.
@Afshin: стирание типа не может работать без знания фактического типа, о котором идет речь. Или, по крайней мере, вы не можете восстановить его, не зная типа. Если вам нужно знать конкретное имя типа, вы не можете использовать лямбду.
Основная проблема здесь заключается в том, что вы хотите использовать лямбду в качестве параметра шаблона типа, а затем вы хотите иметь возможность преобразовать ее в/из void*. Тип лямбды неизвестен, сгенерирован компилятором. И даже если бы он был известен, вы бы не смогли его напечатать.
Поэтому вместо этого вам нужно удалить тип из S и засунуть его в конструктор S:
struct S {
template<typename Func>
S(Func &&fn) : fn_(std::forward<Func>(fn)) {}
auto const& fn() const {
return fn_;
}
std::function<void()> fn_;
}
Вам нужно внутренне стереть тип, чтобы код, пытающийся отменить стирание, имел известный тип для работы.
Да, этот подход возможен только в том случае, если, как вы упомянули, вы знаете тип в конце или можете встроить его в другой известный тип. Моя проблема именно в том, что я не знаю тип (поскольку я не хотел привязывать свой код к конкретному типу) и не могу найти способ встроить его в другой тип (этот случай возможен с std::function, но вы можете легко найти случай, который не является). Я подумал, может быть, есть способ сохранить тип в базовом классе и использовать его, но, похоже, это не так.
@Afshin: вы можете использовать производные от шаблона классы базового класса; это распространенный способ реализации стирания типов. Но вы не воссоздаете первоначальный тип; вы просто помещаете virtual функции в базовый класс, который реализуют производные классы шаблонов, и вызываете эти virtual функции.
Да, проблема в том, что у вас не может быть auto в качестве возвращаемого типа метода virtual в базе. Представьте, что вы хотите, чтобы виртуальный метод получения в базовом классе возвращал шаблонную переменную-член в производном классе, что-то вроде этого: virtual auto get_x() const = 0; что неверно. Если вы знаете типы, вы можете использовать что-то вроде virtual std::string get_x() const = 0; в базовом классе, поэтому нам все равно нужно знать тип, и он не будет полностью универсальным.
std::function
делает это. Это называется стиранием типа.