- Type erasure - техника затирания типов
- Примеры:
std::any, std::function, void* (and std::byte), std::span
- Структура данных, которая позволяет хранить в себе что угодно
#include <any>
#include <vector>
int main() {
std::any a = 5;
std::vector<int> v = {1, 2, 3, 4 ,5};
a = std::move(v);
a = 'a';
std::cout << std::any_cast<char>(a);
// Если мы "не угадаем" тип, то будет exception std::bad_any_cast
}
struct Any {
struct Base {
virtual ~Base() = default;
virtual Base* GetCopy() const = 0;
};
template <typename T>
struct Derived : public Base {
T object;
Derived(const T& object): object(object) {}
Base* GetCopy() const override {
return new Derived<T>(object);
}
};
template <typename U>
Any(const U& object) : ptr(new Derived<U>(object)) {}
Any(const Any& other) : ptr(ptr->GetCopy()) {}
template <typename U>
Any& operator=(const U& object) {
// Must fix copy-assignment operator for any (compare this...)
delete ptr;
ptr = new Derived<U>(object);
}
~Any() {
delete ptr;
}
Base* ptr = nullptr;
};
template <typename T>
T& AnyCast(Any& a) {
auto d_ptr = dynamic_cast<Derived<T>*>(a.ptr);
if (d_ptr == nullptr) {
throw std::bad_any_cast();
}
return d_ptr->obj;
}
#include <functional>
#include <iostream>
int foo(int x) { return x * x; }
int bar(int x) { return x + x; }
struct Callable {
int operator()(int x) { return x * y; }
int method(double x) { return x * x; }
int y = 20;
};
int main() {
std::function<int(int)> f = foo;
std::cout << f(10) << '\n';
f = bar;
std::cout << f(10) << '\n';
Callable c;
f = c;
std::cout << f(10) << '\n';
auto yptr = &Callable::y;
std::cout << std::invoke(yptr, c) << '\n';
c.*yptr;
std::function<int(Callable&)> f1 = &Callable::y;
std::cout << f1(c) << '\n';
auto method_ptr = &Callable::method;
std::cout << std::invoke(method_ptr, c, 2.5) << '\n';
(c.*method_ptr)(2.2);
std::function<int(Callable&, double)> f3 = &Callable::method;
std::function f4 = [&](int y) { return y * y * y; };
}
- То есть
std::invoke вызывает не только функции, но и вычисляет поле класса по данному проектору
#include <functional>
#include <iostream>
struct S {
void foo(int y) { std::cout << x * y; }
int x;
};
int main() {
auto SFoo = &S::foo;
S s{10};
auto SP = &S::x;
std::invoke(SFoo, s, 20);
std::cout << std::invoke(SP, s) << '\n';
}
#include <functional>
#include <iostream>
#include <type_traits>
template <typename T>
struct FunctionHolder {
FunctionHolder(const T& value) : value(value) {}
FunctionHolder(T&& value) : value(std::move(value)) {}
T value;
};
template <typename R, typename F, typename... Args>
R call_function(void* callable, Args... args) {
return std::invoke(static_cast<FunctionHolder<F>*>(callable)->value, args...);
}
template <typename F>
void delete_function(void* callable) {
delete static_cast<FunctionHolder<F>*>(callable);
}
template <typename F>
void copy_function(void*& callable, void* other_callable) {
// using SBO => no void* return (write-in-args)
callable =
new FunctionHolder<F>(*static_cast<FunctionHolder<F>*>(other_callable));
}
template <typename F>
class Function;
template <typename R, typename... Args>
class Function<R(Args...)> {
using call_function_wrapper_t = R (*)(void*, Args...);
using delete_function_wrapper_t = void (*)(void*);
using copy_function_wrapper_t = void (*)(void*&, void*);
public:
template <typename F>
// requires std::is_invocable_r_v<R, F, Args...>
Function(F&& func) {
callable_ = static_cast<void*>(
new FunctionHolder<std::decay_t<F>>(std::forward<F>(func)));
call_function_wrapper = &call_function<R, std::decay_t<F>, Args...>;
delete_function_wrapper = &delete_function<std::decay<F>>;
copy_function_wrapper = ©_function<std::decay_t<F>>;
}
Function(const Function& other) {
call_function_wrapper = other.call_function_wrapper;
delete_function_wrapper = other.delete_function_wrapper;
copy_function_wrapper = other.copy_function_wrapper;
copy_function_wrapper(callable_, other.callable_);
}
Function& operator=(const Function& other) {
if (this == std::addressof(other)) {
return *this;
}
if (call_function_wrapper == other.call_function_wrapper) {
// we can use trivial copy
// must to do
}
delete_function_wrapper(callable_);
other.copy_function_wrapper(callable_, other.callable_);
call_function_wrapper = other.call_function_wrapper;
delete_function_wrapper = other.delete_function_wrapper;
copy_function_wrapper = other.copy_function_wrapper;
}
~Function() {
if (delete_function_wrapper) {
delete_function_wrapper(callable_);
}
}
R operator()(Args... args) {
if (!call_function_wrapper) {
throw "Bad invoke";
}
return call_function_wrapper(callable_, args...);
}
private:
void* callable_ = nullptr;
call_function_wrapper_t call_function_wrapper = nullptr;
delete_function_wrapper_t delete_function_wrapper = nullptr;
copy_function_wrapper_t copy_function_wrapper = nullptr;
};
int main() {
Function<int(int)> f = [](int x) { return x * x; };
std::cout << f(10) << '\n';
return 1;
}
- Этот функционал мы унаследовали из C. Можно объявить такой тип данных
union U {
int x;
double y;
char c;
};
- Когда мы просто объявляем юнион активным членом становится первый член
int main() {
U u; // в данный момент лежит int
std::cout << u.x; // 0
u.d = 3.14; // положили дабл
std::cout << u.d; // вывело 3.14
std::cout << u.x; // UB! (на самом деле будет реинтерпрет каст)
}
- Проблемы появляются когда хотим положить в union поле с нетривиальными конструкторами/деструкторами, например
std::string
union U {
int x;
double y;
std::string s;
U(const char* s): s(s) {}
~U() {}
};
- Возникает следующая проблема: после смены активного члена юнион не уничтожает предыдущий активный член. То есть в следующем коде есть утечка памяти:
union U {
int x;
double d;
std::string s;
U(const char* s): s(s) {}
~U() {}
};
int main() {
U u = "abc";
std::cout << u.s;
u.d = 3.14;
}
- Чтобы это поправить придется вызвать явно деструктор строки:
U u = "abc";
u.s.~basic_string();
u.d = 3.14;
union U {
int x;
double y;
std::string s;
U(double d): y(d) {}
U(const char* s): s(s) {}
~U() {}
};
int main() {
U u = 3.14;
u.s = "abc";
}
- Будет сегфолт потому что под
u.s лежит какой-то мусор, а мы у него вызываем оператор=. Придется воспользоваться placement new
union A {
A () { new (&s1) std::string ("hello world"); }
~A () { s1.~basic_string<char> (); }
int n1;
std::string s1;
};
new (&u.s) std::string("Test");
std::cout << u.s;
- Стоит так же упомянуть про анонимные юнионы:
int main() {
union {
int a;
double b;
};
a = 10;
b = 20;
}
int main() {
std::variant<int, double, std::string> v;
v = "abc";
std::cout << std::get<std::string>(v);
std::cout << std::get<int>(v); // std::bad_variant_access
v = 5; // int положился и строка корректно уничтожился
}
template <typename... Types>
union VariadicUnion {
// тут бы надо определить что такое get от пустого юниона
template <typename T>
void put(const T&) {
static_assert(false, "fail"); // костыль
}
};
template <typename Head, typename... Tail>
union VariadicUnion<Head, Tail...> {
Head head;
VariadicUnion<Tail...> tail;
VariadicUnion() = default;
~VariadicUnion() = default;
template <size_t N>
auto& get() {
if constexpr (N == 0) {
return head;
} else {
return tail.template get<N>();
}
}
template <typename T>
void put(const T& value) {
if constexpr (std::is_same_v<T, Head>) {
new(&head) T(value);
} else {
tail.template put(value);
}
}
};
template <typename T, typename... Types>
struct VariantAlternatives { // новая структура
using Derived = Variant<Types...>;
static constexpr index = index_by_type<T, Types...>;
VariantAlternatives(const T& value) {
auto variant = static_cast<Derived*>(this);
variant->storage.template put<T>(value);
ptr->current_index = index;
}
VariantAlternatives() = default;
~VariantAlternatives() = default;
};
template <typename... Types>
struct Variant
: public VariantAlternatives<Types, Types...>... { // наследование!
VariadicUnion<Types...> storage;
~Variant() {
(VariantAlternatives<Types, Types...>::destroy(), ...);
}
size_t current_index = 0;
using VariantAlternatives<Types, Types...>::VariantAlternatives...; // работает с g++-11
};
template <size_t N, typename... Types>
auto& get(Variant<Types...>& v) {
if (v.current_index == N) {
return storage.template get<N>();
}
throw std::bad_variant_access();
}
// Чтобы сделать get по типу придется написать метафункцию index_by_type которая из пакета аргументов достает i-й тип.
template <size_t N, typename T, typename... Types>
struct index_by_type_impl {
static constexpr size_t value = 0;
};
template <size_t N, typename T, typename Head, typename... Tail>
struct index_by_type_impl<N, T, Head, Tail...> {
static constexpr size_t value = std::is_same_v<T, Head> ? N : index_by_type_impl<N+1, T, Tail...>::value;
};
template <typename T, typename... Types>
static constexpr size_t index_by_type = index_by_type_impl<0, T, Types...>::value;
// Тогда get по типу реализуется очевидно.
// В целом это идейно и есть реализация варианта. Правда у нас тут есть UB. А именно в строчке variant->storage.template put(value);
// В этот момент мы в конструкторе родителя, то есть еще не заинитен наследник. То есть поля storage еще не существует))). Выход следующий: сначала отнаследуемся от VariadicStorage а потом от всех наших родителей.
#pragma once
#include <iostream>
namespace details {
template <typename... Ts>
union VariadicUnion;
template <typename Head, typename... Tail>
union VariadicUnion<Head, Tail...> {
Head value;
VariadicUnion<Tail...> tail;
};
template <>
union VariadicUnion<> {};
} // namespace details
template <typename... Ts>
struct VariantFieldBase {
// details::VariadicUnion<Ts...> data;
std::size_t index = 0;
};
template <typename T, typename Derived>
struct VariantBase {
VariantBase() {}
VariantBase(const T& value) {
std::cout << __PRETTY_FUNCTION__ << '\n';
// static_cast<Derived*>(this)->data;
}
};
template <typename... Ts>
class Variant : public VariantFieldBase<Ts...>, public VariantBase<Ts, Variant<Ts...>>... {
public:
using VariantBase<Ts, Variant<Ts...>>::VariantBase...;
};
// #pragma once
#include <iostream>
template <typename... Ts>
class Variant;
struct BadVariantAccess{};
namespace details {
template <typename... Ts>
union VariadicUnion;
template <typename Head, typename... Tail>
union VariadicUnion<Head, Tail...> {
VariadicUnion() {}
~VariadicUnion() {}
template <typename T>
T& get() {
if constexpr (std::is_same_v<Head, T>) {
return value;
} else {
return tail.template get<T>();
}
}
template <typename T, typename... Args>
T& emplace_construct(Args&&... args) {
if constexpr (std::is_same_v<Head, T>) {
std::construct_at(&value, std::forward<Args>(args)...);
return value;
} else {
return tail.template emplace_construct<T>(std::forward<Args>(args)...);
}
}
void destroy(std::size_t index) {
if (index == 0) {
std::cout << "called destroy at type " << typeid(Head).name() << '\n';
std::destroy_at(&value);
} else {
tail.destroy(index - 1);
}
}
Head value;
VariadicUnion<Tail...> tail;
};
template <>
union VariadicUnion<> {
void destroy(std::size_t) {}
};
} // namespace details
template <typename T>
struct VariantTraitsLength;
template <typename... Ts>
struct VariantTraitsLength<Variant<Ts...>> {
constexpr static std::size_t kValue = sizeof...(Ts);
};
template <typename T, typename Var>
struct VariantTraitsIndex;
template <typename T, typename Head, typename... Tail>
struct VariantTraitsIndex<T, Variant<Head, Tail...>> {
constexpr static std::size_t kValue = 1 + VariantTraitsIndex<T, Variant<Tail...>>::kValue;
};
template <typename Head, typename... Tail>
struct VariantTraitsIndex<Head, Variant<Head, Tail...>> {
constexpr static std::size_t kValue = 0;
};
template <std::size_t I, typename Var>
struct VariantTraitsType;
template <std::size_t I, typename Head, typename... Tail>
struct VariantTraitsType<I, Variant<Head, Tail...>> {
using type = typename VariantTraitsType<I - 1, Variant<Tail...>>::type;
};
template <typename Head, typename... Tail>
struct VariantTraitsType<0, Variant<Head, Tail...>> {
using type = Head;
};
template <typename Var>
struct VariantTraits {
constexpr static std::size_t kLength = VariantTraitsLength<Var>::kValue;
template <typename T>
constexpr static std::size_t kIndex = VariantTraitsIndex<T, Var>::kValue;
template <std::size_t I>
using type = typename VariantTraitsType<I, Var>::type;
};
template <typename... Ts>
struct VariantFieldBase {
details::VariadicUnion<Ts...> data;
std::size_t index = 0;
};
template <typename T, typename Derived>
struct VariantBase {
VariantBase() { }
VariantBase(const T& value) {
static_cast<Derived*>(this)->data.template emplace_construct<T>(value);
static_cast<Derived*>(this)->index = VariantTraits<Derived>::template kIndex<T>;
}
};
template <typename... Ts>
class Variant : public VariantFieldBase<Ts...>, public VariantBase<Ts, Variant<Ts...>>... {
public:
using VariantFieldBase<Ts...>::data;
using VariantFieldBase<Ts...>::index;
using VariantBase<Ts, Variant<Ts...>>::VariantBase...;
~Variant() {
data.destroy(index);
}
template <typename T>
T& get() {
if (index != VariantTraits<Variant>::template kIndex<T>) {
throw BadVariantAccess{};
}
return data.template get<T>();
}
};
static_assert(VariantTraits<Variant<int, double, std::string>>::kLength == 3);
static_assert(VariantTraits<Variant<int, double, std::string>>::kIndex<double> == 1);
static_assert(VariantTraits<Variant<int, double, std::string>>::kIndex<std::string> == 2);
static_assert(std::is_same_v<VariantTraits<Variant<int, double, std::string>>::type<0>, int>);
static_assert(std::is_same_v<VariantTraits<Variant<int, double, std::string>>::type<1>, double>);
static_assert(std::is_same_v<VariantTraits<Variant<int, double, std::string>>::type<2>, std::string>);
int main() {
Variant<int, double, std::string> var("Hello");
std::cout << var.get<std::string>() << '\n';
try {
var.get<int>();
} catch (BadVariantAccess) {
std::cout << "catched BadVariantAccess\n";
}
}