core: standalone any class

This commit is contained in:
Michele Caini
2020-11-30 09:14:44 +01:00
parent 62f40dcb21
commit c95041d7c3
7 changed files with 853 additions and 4 deletions

View File

@@ -13,6 +13,7 @@
* [Wide characters](wide-characters)
* [Conflicts](#conflicts)
* [Monostate](#monostate)
* [Any as in any type](#any-as-in-any-type)
* [Type support](#type-support)
* [Type info](#type-info)
* [Almost unique identifiers](#almost-unique-identifiers)
@@ -214,6 +215,78 @@ const bool b = entt::monostate<"mykey"_hs>{};
const int i = entt::monostate<entt::hashed_string{"mykey"}>{};
```
# Any as in any type
`EnTT` comes with its own `any` type. It may seem redundant considering that
C++17 introduced `std::any`, but it is not (hopefully).<br/>
In fact, the _type_ returned by an `std::any` is a const reference to an
`std::type_info`, an implementation defined class that's not something everyone
wants to see in a software. Furthermore, there is no way to connect it with the
type system of the library and therefore with its integrated RTTI support.<br/>
Note that this class is largely used internally by the library itself.
The API is very similar to that of its most famous counterpart, mainly because
this class serves the same purpose of being an opaque container for any type of
value.<br/>
Instances of `any` also minimize the number of allocations by relying on a well
known technique called _small buffer optimization_ and a fake vtable.
Creating an object of the `any` type, whether empty or not, is trivial:
```cpp
// an empty container
entt::any empty{};
// a container for an int
entt::any any{0};
// in place construction
entt::any in_place{std::in_place_type<int>, 42};
```
The `any` class takes the burden of destroying the contained element when
required, regardless of the storage strategy used for the specific object.<br/>
Furthermore, an instance of `any` is not tied to an actual type. Therefore, the
wrapper will be reconfigured by assigning it an object of a different type than
the one contained, so as to be able to handle the new instance.<br/>
When in doubt about the type of object contained, the `type` member function of
`any` returns an instance of `type_info` associated with its element, or an
invalid `type_info` object if the container is empty.
A particularly interesting feature of this class is that it can also be used as
an opaque container for non-const unmanaged elements:
```cpp
int value;
entt::any any{std::ref(value)};
```
In other words, whenever `any` intercepts a `reference_wrapper`, it acts as a
reference to the original instance rather than making a copy of or moving it
internally. The contained object is never destroyed and users must ensure that
its lifetime exceeds that of the container.<br/>
Similarly, it's possible to create non-owning copies of `any` from an existing
object:
```cpp
// aliasing constructor
entt::any ref = other.ref();
```
In this case, it doesn't matter if the starting container actually holds an
object or acts already as a reference for unmanaged elements, the new instance
thus created won't create copies and will only serve as a reference for the
original item.<br/>
It means that, starting from the example above, both `ref` and` other` will
point to the same object, whether it's initially contained in `other` or already
an unmanaged element.
To cast an instance of `any` to a type, the library offers a set of `any_cast`
functions in all respects similar to their most famous counterparts.<br/>
The only difference is that, in the case of `EnTT`, these won't raise exceptions
but will only cross an assert in debug mode, otherwise resulting in undefined
behavior in case of misuse in release mode.
# Type support
`EnTT` provides some basic information about types of all kinds.<br/>

229
src/entt/core/any.hpp Normal file
View File

@@ -0,0 +1,229 @@
#ifndef ENTT_CORE_ANY_HPP
#define ENTT_CORE_ANY_HPP
#include <functional>
#include <new>
#include <type_traits>
#include <utility>
#include "../config/config.h"
#include "type_info.hpp"
namespace entt {
class any {
enum class operation { COPY, MOVE, DTOR, ADDR, REF, TYPE };
using storage_type = std::aligned_storage_t<sizeof(double[2]), alignof(double[2])>;
using vtable_type = void *(const operation, const any &, void *);
template<typename Type>
static constexpr auto in_situ = sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v<Type>;
template<typename Type>
static void * basic_vtable(const operation op, const any &from, void *to) {
if constexpr(std::is_void_v<Type>) {
return nullptr;
} else if constexpr(std::is_lvalue_reference_v<Type>) {
switch(op) {
case operation::REF:
static_cast<any *>(to)->vtable = from.vtable;
[[fallthrough]];
case operation::COPY:
case operation::MOVE:
static_cast<any *>(to)->instance = from.instance;
[[fallthrough]];
case operation::DTOR:
break;
case operation::ADDR:
return from.instance;
case operation::TYPE:
*static_cast<type_info *>(to) = type_id<std::remove_reference_t<Type>>();
break;
}
} else if constexpr(in_situ<Type>) {
auto *instance = const_cast<Type *>(std::launder(reinterpret_cast<const Type *>(&from.storage)));
switch(op) {
case operation::COPY:
new (&static_cast<any *>(to)->storage) Type{std::as_const(*instance)};
break;
case operation::MOVE:
new (&static_cast<any *>(to)->storage) Type{std::move(*instance)};
[[fallthrough]];
case operation::DTOR:
instance->~Type();
break;
case operation::ADDR:
return instance;
case operation::REF:
static_cast<any *>(to)->vtable = basic_vtable<std::add_lvalue_reference_t<Type>>;
static_cast<any *>(to)->instance = instance;
break;
case operation::TYPE:
*static_cast<type_info *>(to) = type_id<Type>();
break;
}
} else {
switch(op) {
case operation::COPY:
static_cast<any *>(to)->instance = new Type{std::as_const(*static_cast<Type *>(from.instance))};
break;
case operation::MOVE:
static_cast<any *>(to)->instance = from.instance;
break;
case operation::DTOR:
delete static_cast<Type *>(from.instance);
break;
case operation::ADDR:
return from.instance;
case operation::REF:
static_cast<any *>(to)->vtable = basic_vtable<std::add_lvalue_reference_t<Type>>;
static_cast<any *>(to)->instance = from.instance;
break;
case operation::TYPE:
*static_cast<type_info *>(to) = type_id<Type>();
break;
}
}
return nullptr;
}
public:
/*! @brief Default constructor. */
any() ENTT_NOEXCEPT
: vtable{&basic_vtable<void>},
instance{}
{}
template<typename Type, typename... Args>
explicit any(std::in_place_type_t<Type>, [[maybe_unused]] Args &&... args)
: vtable{&basic_vtable<Type>},
instance{}
{
if constexpr(!std::is_void_v<Type>) {
if constexpr(in_situ<Type>) {
new (&storage) Type{std::forward<Args>(args)...};
} else {
instance = new Type{std::forward<Args>(args)...};
}
}
}
template<typename Type>
any(std::reference_wrapper<Type> value)
: vtable{&basic_vtable<std::add_lvalue_reference_t<Type>>},
instance{&value.get()}
{}
template<typename Type, typename = std::enable_if_t<!std::is_same_v<std::remove_cv_t<std::remove_reference_t<Type>>, any>>>
any(Type &&value)
: any{std::in_place_type<std::remove_cv_t<std::remove_reference_t<Type>>>, std::forward<Type>(value)}
{}
any(const any &other)
: any{}
{
vtable = other.vtable;
vtable(operation::COPY, other, this);
}
any(any &&other) ENTT_NOEXCEPT
: any{}
{
vtable = std::exchange(other.vtable, &basic_vtable<void>);
vtable(operation::MOVE, other, this);
}
~any() {
vtable(operation::DTOR, *this, nullptr);
}
any & operator=(any other) {
swap(*this, other);
return *this;
}
[[nodiscard]] const void * data() const ENTT_NOEXCEPT {
return vtable(operation::ADDR, *this, nullptr);
}
[[nodiscard]] void * data() ENTT_NOEXCEPT {
return const_cast<void *>(std::as_const(*this).data());
}
template<typename Type, typename... Args>
void emplace(Args &&... args) {
*this = any{std::in_place_type<Type>, std::forward<Args>(args)...};
}
[[nodiscard]] any ref() const ENTT_NOEXCEPT {
any other{};
vtable(operation::REF, *this, &other);
return other;
}
[[nodiscard]] type_info type() const ENTT_NOEXCEPT {
type_info info;
vtable(operation::TYPE, *this, &info);
return info;
}
[[nodiscard]] explicit operator bool() const ENTT_NOEXCEPT {
return !(vtable(operation::ADDR, *this, nullptr) == nullptr);
}
friend void swap(any &lhs, any &rhs) {
any tmp{};
lhs.vtable(operation::MOVE, lhs, &tmp);
rhs.vtable(operation::MOVE, rhs, &lhs);
lhs.vtable(operation::MOVE, tmp, &rhs);
std::swap(lhs.vtable, rhs.vtable);
}
private:
vtable_type *vtable;
union { void *instance; storage_type storage; };
};
template<typename Type>
Type any_cast(const any &any) ENTT_NOEXCEPT {
ENTT_ASSERT(any.type() == type_id<Type>());
return *static_cast<const Type *>(any.data());
}
template<typename Type>
Type any_cast(any &any) ENTT_NOEXCEPT {
ENTT_ASSERT(any.type() == type_id<Type>());
return *static_cast<Type *>(any.data());
}
template<typename Type>
Type any_cast(any &&any) ENTT_NOEXCEPT {
ENTT_ASSERT(any.type() == type_id<Type>());
return std::move(*static_cast<Type *>(any.data()));
}
template<typename Type>
const Type * any_cast(const any *any) ENTT_NOEXCEPT {
return (any->type() == type_id<Type>() ? static_cast<const Type *>(any->data()) : nullptr);
}
template<typename Type>
Type * any_cast(any *any) ENTT_NOEXCEPT {
return (any->type() == type_id<Type>() ? static_cast<Type *>(any->data()) : nullptr);
}
}
#endif

View File

@@ -1,5 +1,6 @@
#include "config/version.h"
#include "core/algorithm.hpp"
#include "core/any.hpp"
#include "core/attribute.h"
#include "core/family.hpp"
#include "core/hashed_string.hpp"

View File

@@ -38,8 +38,7 @@ class meta_storage {
using vtable_type = void *(const operation, const meta_storage &, meta_storage *);
template<typename Type>
static constexpr auto in_situ = sizeof(Type) <= sizeof(storage_type)
&& std::is_nothrow_move_constructible_v<Type> && std::is_nothrow_copy_constructible_v<Type>;
static constexpr auto in_situ = sizeof(Type) <= sizeof(storage_type) && std::is_nothrow_move_constructible_v<Type>;
template<typename Type>
static void * basic_vtable(const operation op, const meta_storage &from, meta_storage *to) {
@@ -103,7 +102,6 @@ class meta_storage {
}
public:
/*! @brief Default constructor. */
meta_storage() ENTT_NOEXCEPT
: vtable{&basic_vtable<void>},
instance{}
@@ -111,7 +109,8 @@ public:
template<typename Type, typename... Args>
explicit meta_storage(std::in_place_type_t<Type>, [[maybe_unused]] Args &&... args)
: vtable{&basic_vtable<Type>}
: vtable{&basic_vtable<Type>},
instance{}
{
if constexpr(!std::is_void_v<Type>) {
if constexpr(in_situ<Type>) {

View File

@@ -153,6 +153,7 @@ endif()
# Test core
SETUP_BASIC_TEST(algorithm entt/core/algorithm.cpp)
SETUP_BASIC_TEST(any entt/core/any.cpp)
SETUP_BASIC_TEST(family entt/core/family.cpp)
SETUP_BASIC_TEST(hashed_string entt/core/hashed_string.cpp)
SETUP_BASIC_TEST(ident entt/core/ident.cpp)

545
test/entt/core/any.cpp Normal file
View File

@@ -0,0 +1,545 @@
#include <algorithm>
#include <gtest/gtest.h>
#include <entt/core/any.hpp>
struct fat {
double value[4];
inline static int counter = 0;
~fat() { ++counter; }
bool operator==(const fat &other) const {
return std::equal(std::begin(value), std::end(value), std::begin(other.value), std::end(other.value));
}
};
struct empty {
inline static int counter = 0;
~empty() { ++counter; }
};
TEST(Any, SBO) {
entt::any any{'c'};
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::type_id<char>());
ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
ASSERT_EQ(entt::any_cast<char>(any), 'c');
}
TEST(Any, NoSBO) {
fat instance{{.1, .2, .3, .4}};
entt::any any{instance};
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::type_id<fat>());
ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
ASSERT_EQ(entt::any_cast<fat>(any), instance);
}
TEST(Any, Empty) {
entt::any any{};
ASSERT_FALSE(any);
ASSERT_FALSE(any.type());
ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
ASSERT_EQ(any.data(), nullptr);
}
TEST(Any, SBOInPlaceTypeConstruction) {
entt::any any{std::in_place_type<int>, 42};
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::type_id<int>());
ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
ASSERT_EQ(entt::any_cast<int>(any), 42);
auto other = any.ref();
ASSERT_TRUE(other);
ASSERT_EQ(other.type(), entt::type_id<int>());
ASSERT_EQ(entt::any_cast<int>(other), 42);
ASSERT_EQ(other.data(), any.data());
}
TEST(Any, SBOAsRefConstruction) {
int value = 42;
entt::any any{std::ref(value)};
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::type_id<int>());
ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
ASSERT_EQ(entt::any_cast<int>(any), 42);
ASSERT_EQ(any.data(), &value);
auto other = any.ref();
ASSERT_TRUE(other);
ASSERT_EQ(other.type(), entt::type_id<int>());
ASSERT_EQ(entt::any_cast<int>(other), 42);
ASSERT_EQ(other.data(), any.data());
}
TEST(Any, SBOCopyConstruction) {
entt::any any{42};
entt::any other{any};
ASSERT_TRUE(any);
ASSERT_TRUE(other);
ASSERT_EQ(any.type(), entt::type_id<int>());
ASSERT_EQ(other.type(), entt::type_id<int>());
ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
ASSERT_EQ(entt::any_cast<int>(other), 42);
}
TEST(Any, SBOCopyAssignment) {
entt::any any{42};
entt::any other{3};
other = any;
ASSERT_TRUE(any);
ASSERT_TRUE(other);
ASSERT_EQ(any.type(), entt::type_id<int>());
ASSERT_EQ(other.type(), entt::type_id<int>());
ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
ASSERT_EQ(entt::any_cast<int>(other), 42);
}
TEST(Any, SBOMoveConstruction) {
entt::any any{42};
entt::any other{std::move(any)};
ASSERT_FALSE(any);
ASSERT_TRUE(other);
ASSERT_FALSE(any.type());
ASSERT_EQ(other.type(), entt::type_id<int>());
ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
ASSERT_EQ(entt::any_cast<int>(other), 42);
}
TEST(Any, SBOMoveAssignment) {
entt::any any{42};
entt::any other{3};
other = std::move(any);
ASSERT_FALSE(any);
ASSERT_TRUE(other);
ASSERT_FALSE(any.type());
ASSERT_EQ(other.type(), entt::type_id<int>());
ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
ASSERT_EQ(entt::any_cast<int>(other), 42);
}
TEST(Any, SBODirectAssignment) {
entt::any any{};
any = 42;
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::type_id<int>());
ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
ASSERT_EQ(entt::any_cast<int>(any), 42);
}
TEST(Any, NoSBOInPlaceTypeConstruction) {
fat instance{{.1, .2, .3, .4}};
entt::any any{std::in_place_type<fat>, instance};
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::type_id<fat>());
ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
ASSERT_EQ(entt::any_cast<fat>(any), instance);
auto other = any.ref();
ASSERT_TRUE(other);
ASSERT_EQ(other.type(), entt::type_id<fat>());
ASSERT_EQ(entt::any_cast<fat>(other), (fat{{.1, .2, .3, .4}}));
ASSERT_EQ(other.data(), any.data());
}
TEST(Any, NoSBOAsRefConstruction) {
fat instance{{.1, .2, .3, .4}};
entt::any any{std::ref(instance)};
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::type_id<fat>());
ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
ASSERT_EQ(entt::any_cast<fat>(any), instance);
ASSERT_EQ(any.data(), &instance);
auto other = any.ref();
ASSERT_TRUE(other);
ASSERT_EQ(other.type(), entt::type_id<fat>());
ASSERT_EQ(entt::any_cast<fat>(other), (fat{{.1, .2, .3, .4}}));
ASSERT_EQ(other.data(), any.data());
}
TEST(Any, NoSBOCopyConstruction) {
fat instance{{.1, .2, .3, .4}};
entt::any any{instance};
entt::any other{any};
ASSERT_TRUE(any);
ASSERT_TRUE(other);
ASSERT_EQ(any.type(), entt::type_id<fat>());
ASSERT_EQ(other.type(), entt::type_id<fat>());
ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
ASSERT_EQ(entt::any_cast<fat>(other), instance);
}
TEST(Any, NoSBOCopyAssignment) {
fat instance{{.1, .2, .3, .4}};
entt::any any{instance};
entt::any other{3};
other = any;
ASSERT_TRUE(any);
ASSERT_TRUE(other);
ASSERT_EQ(any.type(), entt::type_id<fat>());
ASSERT_EQ(other.type(), entt::type_id<fat>());
ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
ASSERT_EQ(entt::any_cast<fat>(other), instance);
}
TEST(Any, NoSBOMoveConstruction) {
fat instance{{.1, .2, .3, .4}};
entt::any any{instance};
entt::any other{std::move(any)};
ASSERT_FALSE(any);
ASSERT_TRUE(other);
ASSERT_FALSE(any.type());
ASSERT_EQ(other.type(), entt::type_id<fat>());
ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
ASSERT_EQ(entt::any_cast<fat>(other), instance);
}
TEST(Any, NoSBOMoveAssignment) {
fat instance{{.1, .2, .3, .4}};
entt::any any{instance};
entt::any other{3};
other = std::move(any);
ASSERT_FALSE(any);
ASSERT_TRUE(other);
ASSERT_FALSE(any.type());
ASSERT_EQ(other.type(), entt::type_id<fat>());
ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
ASSERT_EQ(entt::any_cast<fat>(other), instance);
}
TEST(Any, NoSBODirectAssignment) {
fat instance{{.1, .2, .3, .4}};
entt::any any{};
any = instance;
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::type_id<fat>());
ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
ASSERT_EQ(entt::any_cast<fat>(any), instance);
}
TEST(Any, VoidInPlaceTypeConstruction) {
entt::any any{std::in_place_type<void>};
ASSERT_FALSE(any);
ASSERT_FALSE(any.type());
ASSERT_EQ(entt::any_cast<int>(&any), nullptr);
}
TEST(Any, VoidCopyConstruction) {
entt::any any{std::in_place_type<void>};
entt::any other{any};
ASSERT_FALSE(any);
ASSERT_FALSE(other);
ASSERT_FALSE(any.type());
ASSERT_FALSE(other.type());
ASSERT_EQ(entt::any_cast<int>(&any), nullptr);
ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
}
TEST(Any, VoidCopyAssignment) {
entt::any any{std::in_place_type<void>};
entt::any other{std::in_place_type<void>};
other = any;
ASSERT_FALSE(any);
ASSERT_FALSE(other);
ASSERT_FALSE(any.type());
ASSERT_FALSE(other.type());
ASSERT_EQ(entt::any_cast<int>(&any), nullptr);
ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
}
TEST(Any, VoidMoveConstruction) {
entt::any any{std::in_place_type<void>};
entt::any other{std::move(any)};
ASSERT_FALSE(any);
ASSERT_FALSE(other);
ASSERT_FALSE(any.type());
ASSERT_FALSE(other.type());
ASSERT_EQ(entt::any_cast<int>(&any), nullptr);
ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
}
TEST(Any, VoidMoveAssignment) {
entt::any any{std::in_place_type<void>};
entt::any other{std::in_place_type<void>};
other = std::move(any);
ASSERT_FALSE(any);
ASSERT_FALSE(other);
ASSERT_FALSE(any.type());
ASSERT_FALSE(other.type());
ASSERT_EQ(entt::any_cast<int>(&any), nullptr);
ASSERT_EQ(entt::any_cast<double>(&other), nullptr);
}
TEST(Any, SBOMoveInvalidate) {
entt::any any{42};
entt::any other{std::move(any)};
entt::any valid = std::move(other);
ASSERT_FALSE(any);
ASSERT_FALSE(other);
ASSERT_TRUE(valid);
}
TEST(Any, NoSBOMoveInvalidate) {
fat instance{{.1, .2, .3, .4}};
entt::any any{instance};
entt::any other{std::move(any)};
entt::any valid = std::move(other);
ASSERT_FALSE(any);
ASSERT_FALSE(other);
ASSERT_TRUE(valid);
}
TEST(Any, VoidMoveInvalidate) {
entt::any any{std::in_place_type<void>};
entt::any other{std::move(any)};
entt::any valid = std::move(other);
ASSERT_FALSE(any);
ASSERT_FALSE(other);
ASSERT_FALSE(valid);
}
TEST(Any, SBODestruction) {
{
entt::any any{empty{}};
empty::counter = 0;
}
ASSERT_EQ(empty::counter, 1);
}
TEST(Any, NoSBODestruction) {
{
entt::any any{fat{}};
fat::counter = 0;
}
ASSERT_EQ(fat::counter, 1);
}
TEST(Any, VoidDestruction) {
// just let asan tell us if everything is ok here
[[maybe_unused]] entt::any any{std::in_place_type<void>};
}
TEST(Any, Emplace) {
entt::any any{};
any.emplace<int>(42);
ASSERT_TRUE(any);
ASSERT_EQ(any.type(), entt::type_id<int>());
ASSERT_EQ(entt::any_cast<double>(&any), nullptr);
ASSERT_EQ(entt::any_cast<int>(any), 42);
}
TEST(Any, EmplaceVoid) {
entt::any any{};
any.emplace<void>();
ASSERT_FALSE(any);
ASSERT_FALSE(any.type());
}
TEST(Any, SBOSwap) {
entt::any lhs{'c'};
entt::any rhs{42};
std::swap(lhs, rhs);
ASSERT_EQ(lhs.type(), entt::type_id<int>());
ASSERT_EQ(rhs.type(), entt::type_id<char>());
ASSERT_EQ(entt::any_cast<char>(&lhs), nullptr);
ASSERT_EQ(entt::any_cast<int>(&rhs), nullptr);
ASSERT_EQ(entt::any_cast<int>(lhs), 42);
ASSERT_EQ(entt::any_cast<char>(rhs), 'c');
}
TEST(Any, NoSBOSwap) {
entt::any lhs{fat{{.1, .2, .3, .4}}};
entt::any rhs{fat{{.4, .3, .2, .1}}};
std::swap(lhs, rhs);
ASSERT_EQ(entt::any_cast<fat>(lhs), (fat{{.4, .3, .2, .1}}));
ASSERT_EQ(entt::any_cast<fat>(rhs), (fat{{.1, .2, .3, .4}}));
}
TEST(Any, VoidSwap) {
entt::any lhs{std::in_place_type<void>};
entt::any rhs{std::in_place_type<void>};
const auto *pre = lhs.data();
std::swap(lhs, rhs);
ASSERT_EQ(pre, lhs.data());
}
TEST(Any, SBOWithNoSBOSwap) {
entt::any lhs{fat{{.1, .2, .3, .4}}};
entt::any rhs{'c'};
std::swap(lhs, rhs);
ASSERT_EQ(lhs.type(), entt::type_id<char>());
ASSERT_EQ(rhs.type(), entt::type_id<fat>());
ASSERT_EQ(entt::any_cast<fat>(&lhs), nullptr);
ASSERT_EQ(entt::any_cast<char>(&rhs), nullptr);
ASSERT_EQ(entt::any_cast<char>(lhs), 'c');
ASSERT_EQ(entt::any_cast<fat>(rhs), (fat{{.1, .2, .3, .4}}));
}
TEST(Any, SBOWithRefSwap) {
int value = 3;
entt::any lhs{std::ref(value)};
entt::any rhs{'c'};
std::swap(lhs, rhs);
ASSERT_EQ(lhs.type(), entt::type_id<char>());
ASSERT_EQ(rhs.type(), entt::type_id<int>());
ASSERT_EQ(entt::any_cast<int>(&lhs), nullptr);
ASSERT_EQ(entt::any_cast<char>(&rhs), nullptr);
ASSERT_EQ(entt::any_cast<char>(lhs), 'c');
ASSERT_EQ(entt::any_cast<int>(rhs), 3);
ASSERT_EQ(rhs.data(), &value);
}
TEST(Any, SBOWithEmptySwap) {
entt::any lhs{'c'};
entt::any rhs{};
std::swap(lhs, rhs);
ASSERT_FALSE(lhs);
ASSERT_EQ(rhs.type(), entt::type_id<char>());
ASSERT_EQ(entt::any_cast<char>(&lhs), nullptr);
ASSERT_EQ(entt::any_cast<double>(&rhs), nullptr);
ASSERT_EQ(entt::any_cast<char>(rhs), 'c');
std::swap(lhs, rhs);
ASSERT_FALSE(rhs);
ASSERT_EQ(lhs.type(), entt::type_id<char>());
ASSERT_EQ(entt::any_cast<double>(&lhs), nullptr);
ASSERT_EQ(entt::any_cast<char>(&rhs), nullptr);
ASSERT_EQ(entt::any_cast<char>(lhs), 'c');
}
TEST(Any, SBOWithVoidSwap) {
entt::any lhs{'c'};
entt::any rhs{std::in_place_type<void>};
std::swap(lhs, rhs);
ASSERT_FALSE(lhs);
ASSERT_EQ(rhs.type(), entt::type_id<char>());
ASSERT_EQ(entt::any_cast<char>(&lhs), nullptr);
ASSERT_EQ(entt::any_cast<double>(&rhs), nullptr);
ASSERT_EQ(entt::any_cast<char>(rhs), 'c');
std::swap(lhs, rhs);
ASSERT_FALSE(rhs);
ASSERT_EQ(lhs.type(), entt::type_id<char>());
ASSERT_EQ(entt::any_cast<double>(&lhs), nullptr);
ASSERT_EQ(entt::any_cast<char>(&rhs), nullptr);
ASSERT_EQ(entt::any_cast<char>(lhs), 'c');
}
TEST(Any, NoSBOWithRefSwap) {
int value = 3;
entt::any lhs{std::ref(value)};
entt::any rhs{fat{{.1, .2, .3, .4}}};
std::swap(lhs, rhs);
ASSERT_EQ(lhs.type(), entt::type_id<fat>());
ASSERT_EQ(rhs.type(), entt::type_id<int>());
ASSERT_EQ(entt::any_cast<int>(&lhs), nullptr);
ASSERT_EQ(entt::any_cast<fat>(&rhs), nullptr);
ASSERT_EQ(entt::any_cast<fat>(lhs), (fat{{.1, .2, .3, .4}}));
ASSERT_EQ(entt::any_cast<int>(rhs), 3);
ASSERT_EQ(rhs.data(), &value);
}
TEST(Any, NoSBOWithEmptySwap) {
entt::any lhs{fat{{.1, .2, .3, .4}}};
entt::any rhs{};
std::swap(lhs, rhs);
ASSERT_FALSE(lhs);
ASSERT_EQ(rhs.type(), entt::type_id<fat>());
ASSERT_EQ(entt::any_cast<fat>(&lhs), nullptr);
ASSERT_EQ(entt::any_cast<double>(&rhs), nullptr);
ASSERT_EQ(entt::any_cast<fat>(rhs), (fat{{.1, .2, .3, .4}}));
std::swap(lhs, rhs);
ASSERT_FALSE(rhs);
ASSERT_EQ(lhs.type(), entt::type_id<fat>());
ASSERT_EQ(entt::any_cast<double>(&lhs), nullptr);
ASSERT_EQ(entt::any_cast<fat>(&rhs), nullptr);
ASSERT_EQ(entt::any_cast<fat>(lhs), (fat{{.1, .2, .3, .4}}));
}
TEST(Any, NoSBOWithVoidSwap) {
entt::any lhs{fat{{.1, .2, .3, .4}}};
entt::any rhs{std::in_place_type<void>};
std::swap(lhs, rhs);
ASSERT_FALSE(lhs);
ASSERT_EQ(rhs.type(), entt::type_id<fat>());
ASSERT_EQ(entt::any_cast<fat>(&lhs), nullptr);
ASSERT_EQ(entt::any_cast<double>(&rhs), nullptr);
ASSERT_EQ(entt::any_cast<fat>(rhs), (fat{{.1, .2, .3, .4}}));
std::swap(lhs, rhs);
ASSERT_FALSE(rhs);
ASSERT_EQ(lhs.type(), entt::type_id<fat>());
ASSERT_EQ(entt::any_cast<double>(&lhs), nullptr);
ASSERT_EQ(entt::any_cast<fat>(&rhs), nullptr);
ASSERT_EQ(entt::any_cast<fat>(lhs), (fat{{.1, .2, .3, .4}}));
}
TEST(Any, AnyCastTemporary) {
ASSERT_EQ(entt::any_cast<int>(42), 42);
}

View File

@@ -35,6 +35,7 @@ struct fat_t: empty_t {
int *foo{nullptr};
int *bar{nullptr};
double gnam[4];
};
struct not_comparable_t {