Unverified Commit 2b8f9e40 authored by pfeatherstone's avatar pfeatherstone Committed by GitHub
Browse files

[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: default avatarpfeatherstone <peter@me>
Co-authored-by: default avatarpf <pf@me>
Co-authored-by: default avatarDavis E. King <davis685@gmail.com>
parent bf410006
...@@ -429,7 +429,6 @@ namespace ...@@ -429,7 +429,6 @@ namespace
b = 3; b = 3;
b = std::move(a); b = std::move(a);
DLIB_TEST(a.get<int>() == 3);
DLIB_TEST(b.get<std::string>() == "asdf"); DLIB_TEST(b.get<std::string>() == "asdf");
} }
...@@ -458,11 +457,214 @@ namespace ...@@ -458,11 +457,214 @@ namespace
DLIB_TEST(!a.contains<ptr_t>()); DLIB_TEST(!a.contains<ptr_t>());
DLIB_TEST(*b.get<ptr_t>() == "asdf"); 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 class type_safe_union_tester : public tester
{ {
......
...@@ -3,9 +3,6 @@ ...@@ -3,9 +3,6 @@
#undef DLIB_TYPE_SAFE_UNION_KERNEl_ABSTRACT_ #undef DLIB_TYPE_SAFE_UNION_KERNEl_ABSTRACT_
#ifdef DLIB_TYPE_SAFE_UNION_KERNEl_ABSTRACT_ #ifdef DLIB_TYPE_SAFE_UNION_KERNEl_ABSTRACT_
#include "../algs.h"
#include "../noncopyable.h"
namespace dlib namespace dlib
{ {
...@@ -21,29 +18,31 @@ namespace dlib ...@@ -21,29 +18,31 @@ namespace dlib
// ---------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------
template < template<typename T>
typename T1, struct in_place_tag {};
typename T2 = _void, // _void indicates parameter not used. /*!
typename T3 = _void, This is an empty class type used as a special disambiguation tag to be
typename T4 = _void, passed as the first argument to the constructor of type_safe_union that performs
typename T5 = _void, in-place construction of an object.
typename T6 = _void,
typename T7 = _void, Here is an example of its usage:
typename T8 = _void,
typename T9 = _void, struct A
typename T10 = _void, {
typename T11 = _void, int i = 0;
typename T12 = _void, int j = 0;
typename T13 = _void,
typename T14 = _void, A(int i_, int j_) : i(i_), j(j_) {}
typename T15 = _void, };
typename T16 = _void,
typename T17 = _void, using tsu = type_safe_union<A,std::string>;
typename T18 = _void,
typename T19 = _void, tsu a(in_place_tag<A>{}, 0, 1);
typename T20 = _void !*/
> // ----------------------------------------------------------------------------------------
class type_safe_union : noncopyable
template <typename... Types>
class type_safe_union
{ {
/*! /*!
REQUIREMENTS ON ALL TEMPLATE ARGUMENTS REQUIREMENTS ON ALL TEMPLATE ARGUMENTS
...@@ -71,55 +70,93 @@ namespace dlib ...@@ -71,55 +70,93 @@ namespace dlib
public: 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( type_safe_union(
); ) = default;
/*! /*!
ensures ensures
- this object is properly initialized - this object is properly initialized
!*/ !*/
template <typename T>
type_safe_union ( type_safe_union (
T&& item const type_safe_union& item
)
/*!
ensures
- copy constructs *this from item
!*/
type_safe_union& operator=(
const type_safe_union& item
); );
/*! /*!
requires
- T must be one of the types given to this object's template arguments
ensures ensures
- this object is properly initialized - copy assigns *this from item
- #get<T>() == item
(i.e. this object will contain a copy of item, or we move item if it's an rvalue)
!*/ !*/
type_safe_union ( type_safe_union (
type_safe_union&& item type_safe_union&& item
); );
/*! /*!
ensures ensures
- move constructs *this from item. - 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
- std::decay_t<T> must be one of the types given to this object's template arguments
ensures
- 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)
!*/
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
- 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( ~type_safe_union(
); );
/*! /*!
...@@ -127,14 +164,36 @@ namespace dlib ...@@ -127,14 +164,36 @@ namespace dlib
- all resources associated with this object have been freed - 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> template <typename T>
static int get_type_id ( static constexpr int get_type_id (
); );
/*! /*!
ensures ensures
- if (T is the same type as one of the template arguments) then - if (T is the same type as one of the template arguments) then
- returns a number indicating which template argument it is. - returns a number indicating which template argument it is. In particular,
(e.g. if T is the same type as T3 then this function returns 3) if it's the first template argument it returns 1, if the second then 2, and so on.
- else - else
- returns -1 - returns -1
!*/ !*/
...@@ -160,68 +219,64 @@ namespace dlib ...@@ -160,68 +219,64 @@ namespace dlib
- returns false - returns false
!*/ !*/
template <typename T> int get_current_type_id(
void apply_to_contents ( ) const;
T& obj
);
/*! /*!
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 ensures
- if (is_empty() == false) then - returns type_identity, i.e, the index of the currently held type.
- Let U denote the type of object currently contained in this type_safe_union For example if the current type is the first template argument it returns 1, if it's the second then 2, and so on.
- calls obj(this->get<U>()) If the current object is empty, i.e. is_empty() == true, then
- The object returned by this->get<U>() will be non-const - returns 0
!*/ !*/
template <typename T> template <typename F>
void apply_to_contents ( auto visit(
const T& obj F&& f
); );
/*! /*!
requires requires
- obj is a function object capable of operating on all the types contained - f is a callable object capable of operating on all the types contained
in this type_safe_union. I.e. obj(this->get<U>()) must be a valid 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. expression for all the possible U types.
ensures ensures
- if (is_empty() == false) then - if (is_empty() == false) then
- Let U denote the type of object currently contained in this type_safe_union - Let U denote the type of object currently contained in this type_safe_union
- calls obj(this->get<U>()) - returns std::forward<F>(f)(this->get<U>())
- The object returned by this->get<U>() will be non-const - The object passed to f() (i.e. by this->get<U>()) will be non-const.
!*/ !*/
template <typename T> template <typename F>
void apply_to_contents ( auto visit(
T& obj F&& f
) const; ) const;
/*! /*!
requires requires
- obj is a function object capable of operating on all the types contained - f is a callable object capable of operating on all the types contained
in this type_safe_union. I.e. obj(this->get<U>()) must be a valid 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. expression for all the possible U types.
ensures ensures
- if (is_empty() == false) then - if (is_empty() == false) then
- Let U denote the type of object currently contained in this type_safe_union - Let U denote the type of object currently contained in this type_safe_union
- calls obj(this->get<U>()) - returns std::forward<F>(f)(this->get<U>())
- The object returned by this->get<U>() will be const - The object passed to f() (i.e. by this->get<U>()) will be const.
!*/ !*/
template <typename T> template <typename F>
void apply_to_contents ( void apply_to_contents(
const T& obj F&& f
);
/*!
ensures:
equivalent to calling visit(std::forward<F>(f)) with void return type
!*/
template <typename F>
void apply_to_contents(
F&& f
) const; ) const;
/*! /*!
requires ensures:
- obj is a function object capable of operating on all the types contained equivalent to calling visit(std::forward<F>(f)) with void return type
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> template <typename T>
...@@ -268,28 +323,6 @@ namespace dlib ...@@ -268,28 +323,6 @@ namespace dlib
- throws bad_type_safe_union_cast - 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 ( void swap (
type_safe_union& item type_safe_union& item
); );
...@@ -340,7 +373,51 @@ namespace dlib ...@@ -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_ #endif // DLIB_TYPE_SAFE_UNION_KERNEl_ABSTRACT_
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment