TICKET 141: now we've full coverage, both for Variant and AccessCasted

Note: the new Variant implementation is a re-write from scratch
and does not rely on util::AccessCasted any more. Anyway, both
are now thoroughly covered by unit test
This commit is contained in:
Fischlurch 2015-04-26 05:55:54 +02:00
parent 3dfac48dea
commit 250a5519de
3 changed files with 184 additions and 89 deletions

View file

@ -2,7 +2,7 @@
ACCESS-CASTED.hpp - util template to access a value using conversion or cast as appropriate
Copyright (C) Lumiera.org
2008, Hermann Vosseler <Ichthyostega@web.de>
2008, 2015 Hermann Vosseler <Ichthyostega@web.de>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
@ -22,19 +22,26 @@
/** @file access-casted.hpp
** Helper for accessing a value, employing either conversion or downcast,
** Helper for accessing a value, employing either a conversion or downcast,
** depending on the relation of the source type (type of the original value)
** and the target type (type we need within the usage context).
** When instantiating AcessCasted<TAR>, we get a template static function
** \c AcessCasted<TAR>::access<SRC>(SRC& elm), but the actual implementation
** is chosen using boost::type_traits. If no sensible implementation can be
** selected, \c EmptyVal<TAR>::create() is invoked instead, which by default
** creates a NULL value or similar by using the no-argument ctor of the
** type TAR. Alternatively, you may define an specialisation of EmptyVal,
** e.g. throwing an exception instead of creating a NULL value.
** \c AcessCasted<TAR>::access<SRC>(SRC&& elm), where the actual implementation
** is chosen using based on our type traits. If no sensible implementation can be
** selected, a static assertion will be triggered.
**
** The possible conversion path is limited to options considered "safe"
** - automatic type conversions, as would happen when returning a value of type SRC
** from a function with return signature TAR
** - this possibly includes an up-cast from an implementation type to a base class
** - explicit copy construction when the type TAR is a plain value
** - downcast to implementation class \em only when a safe dynamic downcast can be
** performed, based on RTTI in the actual object.
** - additionally, a pointer will be formed by taking an address
** - and a reference will be initialised by dereferencing a pointer (with NULL check).
** - if valid and permitted by the rules of the language, r-value references and const
** values, references and pointers-to-const are supported as well.
**
** @todo WIP 4/2015 is being rewritten ///////////////////////////////////////////TICKET #738 needs slight overhaul and clean-up
** @see lumiera::WrapperPtr usage example to access a variant record
** @see lib::InPlaceAnyHolder usage example to access a subclass in embedded storage
**
*/
@ -51,85 +58,96 @@
namespace util {
using std::remove_pointer;
using std::remove_reference;
namespace error = lumiera::error;
template <typename T>
using PlainType = typename remove_pointer<
typename remove_reference<T>::type>::type;
template <typename T>
struct has_RTTI
{
static constexpr bool value = std::is_polymorphic<PlainType<T>>::value;
};
template <typename SRC, typename TAR>
struct can_downcast
{
static constexpr bool value = std::is_base_of<PlainType<SRC>, PlainType<TAR>>::value
&& ( ( std::is_pointer<typename remove_reference<SRC>::type>::value
&& std::is_pointer<typename remove_reference<TAR>::type>::value
)
||( !std::is_pointer<typename remove_reference<SRC>::type>::value
&& !std::is_pointer<typename remove_reference<TAR>::type>::value
));
};
template <typename SRC, typename TAR>
struct can_use_dynamic_downcast
{
static constexpr bool value = !std::is_convertible<SRC,TAR>::value
&& can_downcast<SRC,TAR>::value
&& has_RTTI<SRC>::value
&& has_RTTI<TAR>::value;
};
template <typename SRC, typename TAR>
struct can_use_conversion
: std::is_convertible<SRC,TAR>
{ };
template <typename SRC, typename TAR>
struct can_take_address
{
static constexpr bool value = !std::is_rvalue_reference<SRC>::value
&& !std::is_pointer<typename remove_reference<SRC>::type>::value
&& std::is_pointer<typename remove_reference<TAR>::type>::value;
};
template <typename SRC, typename TAR>
struct can_dereference
{
static constexpr bool value = !std::is_pointer<typename remove_reference<TAR>::type>::value
&& std::is_pointer<typename remove_reference<SRC>::type>::value;
};
template <typename SRC, typename TAR>
struct if_can_use_dynamic_downcast
: std::enable_if< can_use_dynamic_downcast<SRC,TAR>::value, TAR>
{ };
template <typename SRC, typename TAR>
struct if_can_use_conversion
: std::enable_if< can_use_conversion<SRC,TAR>::value, TAR>
{ };
template <typename SRC, typename TAR>
struct if_can_take_address
: std::enable_if< can_take_address<SRC,TAR>::value, TAR>
{ };
template <typename SRC, typename TAR>
struct if_can_dereference
: std::enable_if< can_dereference<SRC,TAR>::value, TAR>
{ };
namespace { // implementation helper traits....
using std::remove_pointer;
using std::remove_reference;
template <typename T>
using PlainType = typename remove_pointer<
typename remove_reference<T>::type>::type;
template <typename T>
struct has_RTTI
{
static constexpr bool value = std::is_polymorphic<PlainType<T>>::value;
};
template <typename SRC, typename TAR>
struct can_downcast
{
static constexpr bool value = std::is_base_of<PlainType<SRC>, PlainType<TAR>>::value
&& ( ( std::is_pointer<typename remove_reference<SRC>::type>::value
&& std::is_pointer<typename remove_reference<TAR>::type>::value
)
||( !std::is_pointer<typename remove_reference<SRC>::type>::value
&& !std::is_pointer<typename remove_reference<TAR>::type>::value
));
};
template <typename SRC, typename TAR>
struct can_use_dynamic_downcast
{
static constexpr bool value = !std::is_convertible<SRC,TAR>::value
&& can_downcast<SRC,TAR>::value
&& has_RTTI<SRC>::value
&& has_RTTI<TAR>::value;
};
template <typename SRC, typename TAR>
struct can_use_conversion
: std::is_convertible<SRC,TAR>
{ };
template <typename SRC, typename TAR>
struct can_take_address
{
static constexpr bool value = !std::is_rvalue_reference<SRC>::value // considered dangerous
&& !std::is_pointer<typename remove_reference<SRC>::type>::value
&& std::is_pointer<typename remove_reference<TAR>::type>::value;
};
template <typename SRC, typename TAR>
struct can_dereference
{
static constexpr bool value = !std::is_pointer<typename remove_reference<TAR>::type>::value
&& std::is_pointer<typename remove_reference<SRC>::type>::value;
};
template <typename SRC, typename TAR>
struct if_can_use_dynamic_downcast
: std::enable_if< can_use_dynamic_downcast<SRC,TAR>::value, TAR>
{ };
template <typename SRC, typename TAR>
struct if_can_use_conversion
: std::enable_if< can_use_conversion<SRC,TAR>::value, TAR>
{ };
template <typename SRC, typename TAR>
struct if_can_take_address
: std::enable_if< can_take_address<SRC,TAR>::value, TAR>
{ };
template <typename SRC, typename TAR>
struct if_can_dereference
: std::enable_if< can_dereference<SRC,TAR>::value, TAR>
{ };
}//(End)helper traits
/**
* Helper template to access a given value,
* possibly converted or casted in a safe way.
*/
template<typename TAR>
struct AccessCasted
{
@ -140,7 +158,6 @@ namespace util {
static typename if_can_use_dynamic_downcast<SRC&&,TAR>::type
access (SRC&& elem)
{
std::cerr << "downcast-"<<lib::test::showRefKind<SRC>()<<" && ->"<<lib::test::showRefKind<SRC&&>()<<std::endl;
try
{
return dynamic_cast<TAR> (std::forward<SRC>(elem));
@ -158,7 +175,6 @@ namespace util {
static typename if_can_use_conversion<SRC&&,TAR>::type
access (SRC&& elem)
{
std::cerr << "convert-"<<lib::test::showRefKind<SRC>()<<" && ->"<<lib::test::showRefKind<SRC&&>()<<std::endl;
return std::forward<SRC> (elem);
}
@ -167,7 +183,6 @@ namespace util {
static typename if_can_take_address<SRC&&,TAR>::type
access (SRC&& elem)
{
std::cerr << "address-"<<lib::test::showRefKind<SRC>()<<" && ->"<<lib::test::showRefKind<SRC&&>()<<std::endl;
return AccessCasted<TAR>::access (&elem);
}
@ -176,7 +191,6 @@ namespace util {
static typename if_can_dereference<SRC&&,TAR>::type
access (SRC&& elem)
{
std::cerr << "deref-"<<lib::test::showRefKind<SRC>()<<" && ->"<<lib::test::showRefKind<SRC&&>()<<std::endl;
if (!elem)
throw error::Invalid("AccessCasted: attempt to build a value or reference from a NULL pointer"
,error::LUMIERA_ERROR_BOTTOM_VALUE);

View file

@ -3,6 +3,83 @@ TESTING "Library Test Suite: basic and low-level components" ./test-suite --grou
TEST "casting or converting access helper" AccessCasted_test <<END
out-lit: can_downcast<B,D> = 1
out-lit: can_downcast<B*,D*> = 1
out-lit: can_downcast<B&,D&> = 1
out-lit: can_downcast<B&,D*> = 0
out-lit: can_downcast<B*,D&> = 0
out-lit: can_downcast<B*&,D*&> = 1
out-lit: can_downcast<D*&,D*&> = 1
out-lit: can_downcast<D*,E*> = 1
out-lit: can_downcast<E*,F*> = 1
out-lit: has_RTTI<D*> = 0
out-lit: has_RTTI<E*> = 1
out-lit: has_RTTI<F*> = 1
out-lit: is_convertible<D,D&> = 0
out-lit: is_convertible<D&,D> = 1
out-lit: can_use_dynamic_downcast<D,D&> = 0
out-lit: can_use_conversion<D,D&> = 0
out-lit: can_use_dynamic_downcast<B*,D*> = 0
out-lit: can_use_conversion<D*,B*> = 1
out-lit: can_use_dynamic_downcast<D*&,D*&> = 0
out-lit: can_use_conversion<D*&,D*&> = 1
out-lit: can_use_conversion<D*,E*> = 0
out-lit: can_use_dynamic_downcast<D*&,E*> = 0
out-lit: === standard case: References ===
out: Access\(D as D&\) .+D.. adr=0x.+ type: D
out: Access\(D& as D&\) .+D.. adr=0x.+ type: D
out-lit: === build a value object ===
out: Access.D as D. .+D.. adr=0x.+ type: D
out: Access.D& as D. .+D.. adr=0x.+ type: D
out: Access.D&& as D. .+D.. adr=0x.+ type: D
out-lit: === take a pointer ===
out: Access.D as D\*. .+0x.+
out: Access.D& as D\*. .+0x.+
out-lit: === dereference a pointer ===
out: Access.D\* as D&. .+D.. adr=0x.+ type: D
out: Access.D\* as D. .+D.. adr=0x.+ type: D
out: Access.D\*&& as D. .+D.. adr=0x.+ type: D
out-lit: === const correctness ===
out: Access.D as D const&. .+D.. adr=0x.+ type: D
out: Access.D& as D const&. .+D.. adr=0x.+ type: D
out: Access.D as const D. .+D.. adr=0x.+ type: D
out: Access.D& as const D. .+D.. adr=0x.+ type: D
out: Access.D as const D\*. .+0x.+
out: Access.D& as const D\*. .+0x.+
out: Access.D\* as D const&. .+D.. adr=0x.+ type: D
out: Access.D\* as const D. .+D.. adr=0x.+ type: D
out: Access.const D as D const&. .+D.. adr=0x.+ type: D
out: Access.D const& as D const&. .+D.. adr=0x.+ type: D
out: Access.const D as const D. .+D.. adr=0x.+ type: D
out: Access.D const& as const D. .+D.. adr=0x.+ type: D
out: Access.const D as const D\*. .+0x.+
out: Access.D const& as const D\*. .+0x.+
out: Access.const D\* as D const&. .+D.. adr=0x.+ type: D
out: Access.const D\* as const D. .+D.. adr=0x.+ type: D
out: Access.D const& as D. .+D.. adr=0x.+ type: D
out: Access.D const&& as D. .+D.. adr=0x.+ type: D
out-lit: === work cases: actual conversions ===
out: Access.B& as B&. .+B.. adr=0x.+ type: B
out: Access.D& as B&. .+B.. adr=0x.+ type: B
out: Access.B\* as B\*. .+0x.+
out: Access.D\* as B\*. .+0x.+
out: Access.D& as B\*. .+0x.+
out: Access.D\* as B&. .+B.. adr=0x.+ type: B
out: Access.B\*& as B\*&. .+0x.+
out: Access.D\*& as D\*&. .+0x.+
out: Access.D& as const B\*. .+0x.+
out: Access.D\* as B const&. .+B.. adr=0x.+ type: B
out: Access.D const& as const B\*. .+0x.+
out: Access.const D\* as B const&. .+B.. adr=0x.+ type: B
out: Access.E\(F\)& as F&. .+F.. adr=0x.+ type: F
out: Access.E\(F\)\* as F\*. .+0x.+
out: Access.E\(F\)\* as F&. .+F.. adr=0x.+ type: F
out: Access.E\(F\)& as F\*. .+0x.+
out: Access.F\\* as X\*. .+0x.+
out: Access.X\(F\)\* as X\*. .+0x.+
out: Access.F\* as B\*. .+0x.+
out-lit: Access(int as double) --->2
out-lit: Access(float as long) --->3
END

View file

@ -47,7 +47,7 @@ namespace util {
namespace test {
@ -89,6 +89,10 @@ namespace test {
* @test verify a helper template for accessing values either through conversion or (dynamic) downcast.
* Typically, this helper is used in value holder containers or variant-like data structures,
* where the actual type is given at instantiation time of the template and possibly erased.
*
* @warning we can not really cover the negative cases, which ought to be rejected by the compiler.
* These cases have been verified one by one, and are retained commented out. You ought
* to re-check these cases manually when using a new compiler.
*/
class AccessCasted_test : public Test
{