[TYPE_SAFE_UNION] upgrade (#2443)

* [TYPE_SAFE_UNION] upgrade

* MSVC doesn't like keyword not

* MSVC doesn't like keyword and

* added tests for emplate(), copy semantics, move semantics, swap, overloaded and apply_to_contents with non void return types

* - didn't need is_void anymore
- added result_of_t
- didn't really need ostream_helper or istream_helper
- split apply_to_contents into apply_to_contents (return void) and visit (return anything so long as visitor is publicly accessible)

* - updated abstract file

* - added get_type_t
- removed deserialize_helper dupplicate
- don't use std::decay_t, that's c++14

* - removed white spaces
- don't need a return-statement when calling apply_to_contents_impl()
- use unchecked_get() whenever possible to minimise explicit use of pointer casting. lets keep that to a minimum

* - added type_safe_union_size
- added type_safe_union_size_v if C++14 is available
- added tests for above

* - test type_safe_union_size_v

* testing nested unions with visitors.

* re-added comment

* added index() in abstract file

* - refactored reset() to clear()
- added comment about clear() in abstract file
- in deserialize(), only reset the object if necessary

* - removed unecessary comment about exceptions
- removed unecessary // -------------
- struct is_valid is not mentioned in abstract. Instead rather requiring T to be a valid type, it is ensured!
- get_type and get_type_t are private. Client code shouldn't need this.
- shuffled some functions around
- type_safe_union_size and type_safe_union_size_v are removed. not needed
- reset() -> clear()
- bug fix in deserialize() index counts from 1, not 0
- improved the abstract file

* refactored index() to get_current_type_id() as per suggestion

* maybe slightly improved docs

* - HURRAY, don't need std::result_of or std::invoke_result for visit() to work. Just privately define your own type trait, in this case called return_type and return_type_t. it works!
- apply_to_contents() now always calls visit()

* example with private visitor using friendship with non-void return types.

* Fix up contracts

It can't be a post condition that T is a valid type, since the choice of T is up to the caller, it's not something these functions decide.  Making it a precondition.

* Update dlib/type_safe_union/type_safe_union_kernel_abstract.h

* Update dlib/type_safe_union/type_safe_union_kernel_abstract.h

* Update dlib/type_safe_union/type_safe_union_kernel_abstract.h

* - added more tests for copy constructors/assignments, move constructors/assignments, and converting constructors/assignments
- helper_copy -> helper_forward
- added validate_type<T> in a couple of places

* - helper_move only takes non-const lvalue references. So we are not using std::move with universal references !
- use enable_if<is_valid<T>> in favor of validate_type<T>()

* - use enable_if<is_valid<T>> in favor of validate_type<T>()

* - added is_valid_check<>. This wraps enable_if<is_valid<T>,bool> and makes use of SFINAE more robust

Co-authored-by: pfeatherstone <peter@me>
Co-authored-by: pf <pf@me>
Co-authored-by: Davis E. King <davis685@gmail.com>
This commit is contained in:
pfeatherstone 2021-10-28 13:34:57 +01:00 committed by GitHub
parent bf4100069f
commit 2b8f9e401a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 932 additions and 635 deletions

View File

@ -429,7 +429,6 @@ namespace
b = 3;
b = std::move(a);
DLIB_TEST(a.get<int>() == 3);
DLIB_TEST(b.get<std::string>() == "asdf");
}
@ -458,12 +457,215 @@ namespace
DLIB_TEST(!a.contains<ptr_t>());
DLIB_TEST(*b.get<ptr_t>() == "asdf");
}
{
//testing copy semantics and move semantics
struct mytype
{
mytype(int i_ = 0) : i(i_) {}
mytype(const mytype& other) : i(other.i) {}
mytype& operator=(const mytype& other) {i = other.i; return *this;}
mytype(mytype&& other) : i(other.i) {other.i = 0;}
mytype& operator=(mytype&& other) {i = other.i ; other.i = 0; return *this;}
int i = 0;
};
using tsu = type_safe_union<int,mytype>;
{
mytype a(10);
tsu ta(a); //copy constructor
DLIB_TEST(a.i == 10);
DLIB_TEST(ta.cast_to<mytype>().i == 10);
}
{
mytype a(10);
tsu ta;
ta = a; //copy assign
DLIB_TEST(a.i == 10);
DLIB_TEST(ta.cast_to<mytype>().i == 10);
}
{
mytype a(10);
tsu ta(std::move(a)); //move constructor
DLIB_TEST(a.i == 0);
DLIB_TEST(ta.cast_to<mytype>().i == 10);
}
{
mytype a(10);
tsu ta;
ta = std::move(a); //move assign
DLIB_TEST(a.i == 0);
DLIB_TEST(ta.cast_to<mytype>().i == 10);
}
{
tsu ta(mytype(10));
DLIB_TEST(ta.cast_to<mytype>().i == 10);
tsu tb(ta); //copy constructor
DLIB_TEST(ta.cast_to<mytype>().i == 10);
DLIB_TEST(tb.cast_to<mytype>().i == 10);
}
{
tsu ta(mytype(10));
DLIB_TEST(ta.cast_to<mytype>().i == 10);
tsu tb;
tb = ta; //copy assign
DLIB_TEST(ta.cast_to<mytype>().i == 10);
DLIB_TEST(tb.cast_to<mytype>().i == 10);
}
{
tsu ta(mytype(10));
DLIB_TEST(ta.cast_to<mytype>().i == 10);
tsu tb(std::move(ta)); //move constructor
DLIB_TEST(ta.is_empty());
DLIB_TEST(tb.cast_to<mytype>().i == 10);
}
{
tsu ta(mytype(10));
DLIB_TEST(ta.cast_to<mytype>().i == 10);
tsu tb;
tb = std::move(ta); //move assign
DLIB_TEST(ta.is_empty());
DLIB_TEST(tb.cast_to<mytype>().i == 10);
}
}
{
//testing emplace(), copy semantics, move semantics, swap, overloaded, and new visitor
type_safe_union<int, float, std::string> a, b;
a.emplace<std::string>("hello world");
DLIB_TEST(a.contains<std::string>());
b = a; //copy
DLIB_TEST(a.contains<std::string>());
DLIB_TEST(b.contains<std::string>());
DLIB_TEST(a.cast_to<std::string>() == "hello world");
DLIB_TEST(b.cast_to<std::string>() == "hello world");
a = 1;
DLIB_TEST(a.contains<int>());
DLIB_TEST(a.cast_to<int>() == 1);
b = std::move(a);
DLIB_TEST(b.contains<int>());
DLIB_TEST(b.cast_to<int>() == 1);
DLIB_TEST(a.is_empty());
DLIB_TEST(a.get_current_type_id() == 0);
swap(a, b);
DLIB_TEST(a.contains<int>());
DLIB_TEST(a.cast_to<int>() == 1);
DLIB_TEST(b.is_empty());
DLIB_TEST(b.get_current_type_id() == 0);
//visit can return non-void types
auto ret = a.visit(overloaded(
[](int) {
return std::string("int");
},
[](float) {
return std::string("float");
},
[](const std::string&) {
return std::string("std::string");
}
));
static_assert(std::is_same<std::string, decltype(ret)>::value, "bad return type");
DLIB_TEST(ret == "int");
//apply_to_contents can only return void
a = std::string("hello there!");
std::string str;
a.apply_to_contents(overloaded(
[&str](int) {
str = std::string("int");
},
[&str](float) {
str = std::string("float");
},
[&str](const std::string& item) {
str = item;
}
));
DLIB_TEST(str == "hello there!");
}
{
//nested unions
using tsu_a = type_safe_union<int,float,std::string>;
using tsu_b = type_safe_union<int,float,std::string,tsu_a>;
tsu_b object(dlib::in_place_tag<tsu_a>{}, std::string("hello from bottom node"));
DLIB_TEST(object.contains<tsu_a>());
DLIB_TEST(object.get<tsu_a>().get<std::string>() == "hello from bottom node");
auto ret = object.visit(overloaded(
[](int) {
return std::string("int");
},
[](float) {
return std::string("float");
},
[](std::string) {
return std::string("std::string");
},
[](const tsu_a& item) {
return item.visit(overloaded(
[](int) {
return std::string("nested int");
},
[](float) {
return std::string("nested float");
},
[](std::string str) {
return str;
}
));
}
));
static_assert(std::is_same<std::string, decltype(ret)>::value, "bad type");
DLIB_TEST(ret == "hello from bottom node");
}
{
//"private" visitor
using tsu = type_safe_union<int,float,std::string>;
class visitor_private
{
private:
std::string operator()(int)
{
return std::string("int");
}
std::string operator()(float)
{
return std::string("float");
}
std::string operator()(const std::string& str)
{
return str;
}
friend tsu;
};
visitor_private visitor;
tsu a = std::string("hello from private visitor");
auto ret = a.visit(visitor);
static_assert(std::is_same<std::string, decltype(ret)>::value, "bad type");
DLIB_TEST(ret == "hello from private visitor");
}
}
};
class type_safe_union_tester : public tester
{
public:

File diff suppressed because it is too large Load Diff

View File

@ -3,9 +3,6 @@
#undef DLIB_TYPE_SAFE_UNION_KERNEl_ABSTRACT_
#ifdef DLIB_TYPE_SAFE_UNION_KERNEl_ABSTRACT_
#include "../algs.h"
#include "../noncopyable.h"
namespace dlib
{
@ -21,29 +18,31 @@ namespace dlib
// ----------------------------------------------------------------------------------------
template <
typename T1,
typename T2 = _void, // _void indicates parameter not used.
typename T3 = _void,
typename T4 = _void,
typename T5 = _void,
typename T6 = _void,
typename T7 = _void,
typename T8 = _void,
typename T9 = _void,
typename T10 = _void,
typename T11 = _void,
typename T12 = _void,
typename T13 = _void,
typename T14 = _void,
typename T15 = _void,
typename T16 = _void,
typename T17 = _void,
typename T18 = _void,
typename T19 = _void,
typename T20 = _void
>
class type_safe_union : noncopyable
template<typename T>
struct in_place_tag {};
/*!
This is an empty class type used as a special disambiguation tag to be
passed as the first argument to the constructor of type_safe_union that performs
in-place construction of an object.
Here is an example of its usage:
struct A
{
int i = 0;
int j = 0;
A(int i_, int j_) : i(i_), j(j_) {}
};
using tsu = type_safe_union<A,std::string>;
tsu a(in_place_tag<A>{}, 0, 1);
!*/
// ----------------------------------------------------------------------------------------
template <typename... Types>
class type_safe_union
{
/*!
REQUIREMENTS ON ALL TEMPLATE ARGUMENTS
@ -71,53 +70,91 @@ namespace dlib
public:
typedef T1 type1;
typedef T2 type2;
typedef T3 type3;
typedef T4 type4;
typedef T5 type5;
typedef T6 type6;
typedef T7 type7;
typedef T8 type8;
typedef T9 type9;
typedef T10 type10;
typedef T11 type11;
typedef T12 type12;
typedef T13 type13;
typedef T14 type14;
typedef T15 type15;
typedef T16 type16;
typedef T17 type17;
typedef T18 type18;
typedef T19 type19;
typedef T20 type20;
type_safe_union(
);
) = default;
/*!
ensures
- this object is properly initialized
!*/
template <typename T>
type_safe_union (
const type_safe_union& item
)
/*!
ensures
- copy constructs *this from item
!*/
type_safe_union& operator=(
const type_safe_union& item
);
/*!
ensures
- copy assigns *this from item
!*/
type_safe_union (
type_safe_union&& item
);
/*!
ensures
- move constructs *this from item.
!*/
type_safe_union& operator= (
type_safe_union&& item
);
/*!
ensures
- move assigns *this from item.
!*/
template <
typename T
>
type_safe_union (
T&& item
);
/*!
requires
- T must be one of the types given to this object's template arguments
- std::decay_t<T> must be one of the types given to this object's template arguments
ensures
- this object is properly initialized
- #get<T>() == item
(i.e. this object will contain a copy of item, or we move item if it's an rvalue)
- constructs *this from item using perfect forwarding (converting constructor)
- #get<T>() == std::forward<T>(item)
(i.e. this object will either contain a copy of item or will have moved item into *this
depending on the reference type)
!*/
type_safe_union (
type_safe_union&& item
);
template <
typename T
>
type_safe_union& operator= (
T&& item
);
/*!
requires
- std::decay_t<T> must be one of the types given to this object's template arguments
ensures
- move constructs *this from item.
- assigns *this from item using perfect forwarding (converting assignment)
- #get<T> == std::forward<T>(item)
(i.e. this object will either contain a copy of item or will have moved item into *this
depending on the reference type)
!*/
template <
typename T,
typename... Args
>
type_safe_union (
in_place_tag<T>,
Args&&... args
);
/*!
requires
- T must be one of the types given to this object's template arguments
ensures
- constructs *this with type T using constructor-arguments args...
(i.e. efficiently performs *this = T(args...))
!*/
~type_safe_union(
@ -127,14 +164,36 @@ namespace dlib
- all resources associated with this object have been freed
!*/
void clear();
/*!
ensures
- all resources associated with this object have been freed
- #is_empty() == true
!*/
template <
typename T,
typename... Args
>
void emplace(
Args&&... args
);
/*!
requires
- T must be one of the types given to this object's template arguments
ensures
- re-assigns *this with type T using constructor-arguments args...
(i.e. efficiently performs *this = T(args...))
!*/
template <typename T>
static int get_type_id (
static constexpr int get_type_id (
);
/*!
ensures
- if (T is the same type as one of the template arguments) then
- returns a number indicating which template argument it is.
(e.g. if T is the same type as T3 then this function returns 3)
- returns a number indicating which template argument it is. In particular,
if it's the first template argument it returns 1, if the second then 2, and so on.
- else
- returns -1
!*/
@ -160,68 +219,64 @@ namespace dlib
- returns false
!*/
template <typename T>
void apply_to_contents (
T& obj
int get_current_type_id(
) const;
/*!
ensures
- returns type_identity, i.e, the index of the currently held type.
For example if the current type is the first template argument it returns 1, if it's the second then 2, and so on.
If the current object is empty, i.e. is_empty() == true, then
- returns 0
!*/
template <typename F>
auto visit(
F&& f
);
/*!
requires
- obj is a function object capable of operating on all the types contained
in this type_safe_union. I.e. obj(this->get<U>()) must be a valid
- f is a callable object capable of operating on all the types contained
in this type_safe_union. I.e. std::forward<F>(f)(this->get<U>()) must be a valid
expression for all the possible U types.
ensures
- if (is_empty() == false) then
- Let U denote the type of object currently contained in this type_safe_union
- calls obj(this->get<U>())
- The object returned by this->get<U>() will be non-const
- returns std::forward<F>(f)(this->get<U>())
- The object passed to f() (i.e. by this->get<U>()) will be non-const.
!*/
template <typename T>
void apply_to_contents (
const T& obj
template <typename F>
auto visit(
F&& f
) const;
/*!
requires
- f is a callable object capable of operating on all the types contained
in this type_safe_union. I.e. std::forward<F>(f)(this->get<U>()) must be a valid
expression for all the possible U types.
ensures
- if (is_empty() == false) then
- Let U denote the type of object currently contained in this type_safe_union
- returns std::forward<F>(f)(this->get<U>())
- The object passed to f() (i.e. by this->get<U>()) will be const.
!*/
template <typename F>
void apply_to_contents(
F&& f
);
/*!
requires
- obj is a function object capable of operating on all the types contained
in this type_safe_union. I.e. obj(this->get<U>()) must be a valid
expression for all the possible U types.
ensures
- if (is_empty() == false) then
- Let U denote the type of object currently contained in this type_safe_union
- calls obj(this->get<U>())
- The object returned by this->get<U>() will be non-const
ensures:
equivalent to calling visit(std::forward<F>(f)) with void return type
!*/
template <typename T>
void apply_to_contents (
T& obj
template <typename F>
void apply_to_contents(
F&& f
) const;
/*!
requires
- obj is a function object capable of operating on all the types contained
in this type_safe_union. I.e. obj(this->get<U>()) must be a valid
expression for all the possible U types.
ensures
- if (is_empty() == false) then
- Let U denote the type of object currently contained in this type_safe_union
- calls obj(this->get<U>())
- The object returned by this->get<U>() will be const
!*/
template <typename T>
void apply_to_contents (
const T& obj
) const;
/*!
requires
- obj is a function object capable of operating on all the types contained
in this type_safe_union. I.e. obj(this->get<U>()) must be a valid
expression for all the possible U types.
ensures
- if (is_empty() == false) then
- Let U denote the type of object currently contained in this type_safe_union
- calls obj(this->get<U>())
- The object returned by this->get<U>() will be const
ensures:
equivalent to calling visit(std::forward<F>(f)) with void return type
!*/
template <typename T>
@ -268,28 +323,6 @@ namespace dlib
- throws bad_type_safe_union_cast
!*/
template <typename T>
type_safe_union& operator= (
T&& item
);
/*!
requires
- T must be one of the types given to this object's template arguments
ensures
- #get<T>() == item
(i.e. this object will contain a copy of item, or we move item if it's an rvalue)
- returns *this
!*/
type_safe_union& operator= (
type_safe_union&& item
);
/*!
ensures
- Allows moving item into *this. In fact, this is done by this->swap(item).
- returns *this
!*/
void swap (
type_safe_union& item
);
@ -340,7 +373,51 @@ namespace dlib
// ----------------------------------------------------------------------------------------
template<typename... T>
overloaded_helper<typename std::decay<T>::type...> overloaded(T&&... t)
{
return overloaded_helper<typename std::decay<T>::type...>{std::forward<T>(t)...};
}
/*!
This is a helper function for passing many callable objects (usually lambdas)
to either apply_to_contents() or visit(), that combine to make a complete
visitor. A picture paints a thousand words:
using tsu = type_safe_union<int,float,std::string>;
tsu a = std::string("hello there");
std::string result;
a.apply_to_contents(overloaded(
[&result](int) {
result = std::string("int");
},
[&result](float) {
result = std::string("float");
},
[&result](const std::string& item) {
result = item;
}
));
assert(result == "hello there");
result = "";
result = a.visit(overloaded(
[](int) {
return std::string("int");
},
[](float) {
return std::string("float");
},
[](const std::string& item) {
return item;
}
));
assert(result == "hello there");
!*/
}
#endif // DLIB_TYPE_SAFE_UNION_KERNEl_ABSTRACT_