lumiera_/src/lib/meta/trait.hpp

684 lines
20 KiB
C++
Raw Normal View History

/*
TRAIT.hpp - type handling and type detection helpers
2010-12-17 23:28:49 +01:00
Copyright: clarify and simplify the file headers * Lumiera source code always was copyrighted by individual contributors * there is no entity "Lumiera.org" which holds any copyrights * Lumiera source code is provided under the GPL Version 2+ == Explanations == Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above. For this to become legally effective, the ''File COPYING in the root directory is sufficient.'' The licensing header in each file is not strictly necessary, yet considered good practice; attaching a licence notice increases the likeliness that this information is retained in case someone extracts individual code files. However, it is not by the presence of some text, that legally binding licensing terms become effective; rather the fact matters that a given piece of code was provably copyrighted and published under a license. Even reformatting the code, renaming some variables or deleting parts of the code will not alter this legal situation, but rather creates a derivative work, which is likewise covered by the GPL! The most relevant information in the file header is the notice regarding the time of the first individual copyright claim. By virtue of this initial copyright, the first author is entitled to choose the terms of licensing. All further modifications are permitted and covered by the License. The specific wording or format of the copyright header is not legally relevant, as long as the intention to publish under the GPL remains clear. The extended wording was based on a recommendation by the FSF. It can be shortened, because the full terms of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
Copyright (C)
2009, Hermann Vosseler <Ichthyostega@web.de>
2010-12-17 23:28:49 +01:00
Copyright: clarify and simplify the file headers * Lumiera source code always was copyrighted by individual contributors * there is no entity "Lumiera.org" which holds any copyrights * Lumiera source code is provided under the GPL Version 2+ == Explanations == Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above. For this to become legally effective, the ''File COPYING in the root directory is sufficient.'' The licensing header in each file is not strictly necessary, yet considered good practice; attaching a licence notice increases the likeliness that this information is retained in case someone extracts individual code files. However, it is not by the presence of some text, that legally binding licensing terms become effective; rather the fact matters that a given piece of code was provably copyrighted and published under a license. Even reformatting the code, renaming some variables or deleting parts of the code will not alter this legal situation, but rather creates a derivative work, which is likewise covered by the GPL! The most relevant information in the file header is the notice regarding the time of the first individual copyright claim. By virtue of this initial copyright, the first author is entitled to choose the terms of licensing. All further modifications are permitted and covered by the License. The specific wording or format of the copyright header is not legally relevant, as long as the intention to publish under the GPL remains clear. The extended wording was based on a recommendation by the FSF. It can be shortened, because the full terms of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00
  **Lumiera** is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version. See the file COPYING for further details.
2010-12-17 23:28:49 +01:00
*/
/** @file trait.hpp
** Helpers for type detection, type rewriting and metaprogramming.
** This header is a collection of frequently used templates for working with types.
** It incurs only modest header inclusion overhead
** @warning be sure not to jeopardise that!
**
** \par unwrapping
** Strip away all kinds of type adornments, like const, reference, pointer, smart-ptr.
** The accompanying \ref lib::meta::unwrap() function can be used to accept "stuff
** packaged in various forms". The \ref Strip template packages this ability in various
** degrees for metaprogramming
** @warning these helpers can be quite dangerous, as they silently break
** any protective barriers (including lifecycle managing smart-ptrs)
**
** \par string conversion
** a set of trait templates to categorise arbitrary types with respect to
** the ability for string conversions
**
** \par ability to iterate
** these traits [can be used](\ref util-foreach.hpp) to build the notion of a
** generic container -- basically anything that can be enumerated.
** Within Lumiera, we frequently use our own concept of "iterability",
** known as ["Lumiera Forward Iterator"](\ref iter-adapter.hpp). These
** helpers here allow to unify this concept with the "Range"
** concept from the standard library (`begin()` and `end()`)
**
** @see MetaUtils_test
** @see format-obj.hpp string representation for _anything_
** @see meta/util.hpp very basic metaprogramming helpers
** @see typelist.hpp
**
*/
#ifndef LIB_META_TRAIT_H
#define LIB_META_TRAIT_H
#include "lib/meta/util.hpp"
#include "lib/meta/duck-detector.hpp"
#include <type_traits>
//Forward declarations for the Unwrap helper....
namespace boost{
template<class X> class reference_wrapper;
}
namespace std {
template<class X> class reference_wrapper;
template<class X> class shared_ptr;
template<class X, class D> class unique_ptr;
template<class C, class T> class basic_filebuf;
template<class C> class char_traits;
}
namespace lib{
template<class X, class B> class P;
namespace hash {
class LuidH;
}
namespace time {
class TimeValue;
class Duration;
}}
namespace steam {
namespace mobject{
template<class X, class B> class Placement;
}}
namespace lib {
namespace meta {
using std::remove_cv_t;
using std::remove_pointer_t;
using std::remove_reference_t;
using std::conditional_t;
using std::is_reference_v;
using std::is_lvalue_reference_v;
using std::is_rvalue_reference_v;
using std::is_pointer;
using std::is_base_of;
using std::is_convertible;
using std::is_constructible;
using std::is_floating_point;
using std::is_arithmetic;
using std::is_unsigned;
using std::is_signed;
using std::is_class;
using std::is_same;
using std::__not_;
using std::__and_;
using std::__or_;
template<typename T>
static constexpr bool isConst_v = std::is_const_v<remove_reference_t<T>>;
template<typename T>
static constexpr bool isLRef_v = std::is_lvalue_reference_v<T>;
template<typename T>
static constexpr bool isRRef_v = std::is_rvalue_reference_v<T>;
template<typename T>
static constexpr bool isRef_v = std::is_reference_v<T>;
/**
* Helper for type analysis and convenience accessors:
* attempts to extract a base type from various wrappers.
* Additionally allows to extract/deref the wrapped element.
* @note can also be used as a meta function to detect "anything wrapped"
* @warning strips away any const
* @warning also strips away smart-ptrs and lifecycle managers!
*/
template<typename X>
struct Unwrap
: std::false_type
{
using Type = X;
static X&
extract (X const& x)
{
return const_cast<X&> (x);
}
};
template<>
struct Unwrap<void> ///< @note we can't unwrap void!
: std::false_type
{
using Type = void;
};
template<typename X>
struct Unwrap<X*>
: std::true_type
{
using Type = remove_cv_t<X>;
static Type&
extract (const X* ptr)
{
ASSERT (ptr);
return const_cast<Type&> (*ptr);
}
};
template<typename X>
2016-12-23 04:23:03 +01:00
struct Unwrap<boost::reference_wrapper<X>>
: std::true_type
{
using Type = X;
static X&
extract (boost::reference_wrapper<X> wrapped)
{
return wrapped;
}
};
template<typename X>
2016-12-23 04:23:03 +01:00
struct Unwrap<std::reference_wrapper<X>>
: std::true_type
{
using Type = X;
static X&
extract (std::reference_wrapper<X> wrapped)
{
return wrapped;
}
};
template<typename X, class D>
struct Unwrap<std::unique_ptr<X,D>>
: std::true_type
{
using Type = X;
static X&
extract (std::unique_ptr<X,D> const& ptr)
{
ASSERT (ptr);
return *ptr;
}
};
template<typename X>
2016-12-23 04:23:03 +01:00
struct Unwrap<std::shared_ptr<X>>
: std::true_type
{
using Type = X;
static X&
extract (std::shared_ptr<X> const& ptr)
{
ASSERT (ptr);
return *ptr;
}
};
template<typename X, class B>
2016-12-23 04:23:03 +01:00
struct Unwrap<P<X, B>>
: std::true_type
{
using Type = X;
static X&
extract (P<X,B> ptr)
{
ASSERT (ptr);
return *ptr;
}
};
/** convenience shortcut: unwrapping free function.
* @return reference to the bare element.
* @warning this function is dangerous: it strips away
* any managing smart-ptr and any const!
* You might even access and return a
* reference to an anonymous temporary.
*/
template<typename X>
Unwrap<X>::Type&
unwrap (X const& wrapped)
{
return Unwrap<X>::extract(wrapped);
}
/** Helper for type analysis: tries to strip all kinds of type adornments */
template<typename X>
struct Strip
{
using TypeUnconst = conditional_t<is_reference_v<X>
, conditional_t<is_rvalue_reference_v<X>
, remove_cv_t<remove_reference_t<X>> &&
, remove_cv_t<remove_reference_t<X>> & >
, remove_cv_t<X>>;
using TypeReferred = remove_reference_t<TypeUnconst>;
using TypePointee = remove_pointer_t<TypeReferred>;
using TypePlain = remove_cv_t<TypePointee>;
using Type = Unwrap<TypePlain>::Type;
};
/** Type definition helper for pointer and reference types.
* Allows to create a member field and to get the basic type
* irrespective if the given type is plain, pointer or reference
* @note we _do treat pointers specific_ though; a pointer is itself
* a value and the pointer-indirection is _not_ stripped.
* (use meta::Strip to radically strip all adornments)
*/
template<typename TY>
struct RefTraits
{
typedef TY Value;
typedef TY* Pointer;
typedef TY& Reference;
};
template<typename TY>
struct RefTraits<TY *>
{
typedef TY* Value;
typedef TY** Pointer;
typedef TY*& Reference;
};
template<typename TY>
struct RefTraits<TY &>
{
typedef TY Value;
typedef TY* Pointer;
typedef TY& Reference;
};
template<typename TY>
struct RefTraits<TY &&>
{
typedef TY Value;
typedef TY* Pointer;
typedef TY& Reference;
};
/* ==== Traits ==== */
/** compare unadorned types, disregarding const and references */
template<typename T, typename U>
struct is_basicallySame
: is_same <typename Strip<T>::TypeReferred
,typename Strip<U>::TypeReferred>
{ };
/** verify compliance to an interface by subtype check */
template<typename S, typename I>
struct is_Subclass
: __or_< __and_< is_class<I>
, is_class<S>
, is_base_of<I,S>
>
, is_same<I,S>
>
{ };
/** compare for unadorned base type, disregarding const and references */
template<typename S, typename I>
struct is_basically
: is_Subclass <typename Strip<S>::TypeReferred
,typename Strip<I>::TypeReferred>
{ };
/** verify the first (special) type can stand-in for the second */
template<typename S, typename G>
struct can_StandIn
: std::is_convertible<typename RefTraits<S>::Reference
,typename RefTraits<G>::Reference
>
{ };
/** detect various flavours of string / text data */
template<typename X>
struct is_StringLike
: __or_< is_basically<X, std::string>
, is_basically<X, std::string_view>
, is_convertible<X, const char*>
>
{ };
/** types able to be lexically converted to string representation
* @note this compile-time trait can't predict if such an conversion
* to string will be successful at runtime; indeed it may throw,
* so you should additionally guard the invocation with try-catch!
* @remarks this template is relevant for guarding `lexical_cast<..>` expressions.
* Such an expression won't even compile for some types, because of missing or
* ambiguous output operator(s). Ideally, there would be some automatic detection
* (relying on the existence of an `operator<<` for the given type. But at my
* first attempt in 2009 (commit 1533e5bd0) I couldn't make this work, so I
* fell back on just declaring all classes of types which are known to work
* with lexical_cast to string.
* @remarks Meanwhile (2016) I think this is an adequate and robust solution
* and here to stay. Based on this, I'll add a generic overload for the
* output stream inserter `operator<<` to use custom string conversions;
* this trait is essential to exclude types which can be printed as-is.
*/
template<typename X>
struct can_lexical2string
: __or_< is_arithmetic<X>
, is_StringLike<X>
>
{ };
template<typename X>
struct use_LexicalConversion
: __and_<can_lexical2string<X>
,__not_<can_convertToString<X>>
>
{ };
// need to exclude files and input streams from automatic string conversion
template<typename X>
struct is_StreamSource
: is_same<X, std::basic_filebuf<char, std::char_traits<char> > >
{ };
/** when to use custom string conversions for output streams */
template<typename X>
struct use_StringConversion4Stream
: __and_<is_class<typename Strip<X>::TypePlain>
,__not_<is_pointer<X>>
,__not_<can_lexical2string<X>>
,__not_<is_StreamSource<X>>
>
{ };
/** detect smart pointers */
template<typename X>
struct is_smart_ptr
: std::false_type
{ };
template<typename T>
struct is_smart_ptr<std::shared_ptr<T>>
: std::true_type
{ };
template <typename T, typename D>
struct is_smart_ptr<std::unique_ptr<T,D>>
: std::true_type
{ };
template<typename NUM>
struct is_nonFloat
: __and_<is_arithmetic<NUM>
,__not_<is_floating_point<NUM>>
>
{ };
/** temporary workaround for GCC [Bug-63723], necessary until CGG-5
* @remarks The problem is that GCC emits a warning on narrowing conversion,
* instead of letting the SFINAE substitution fail. This can be considered
* questionable behaviour, since the usual implementation of a `is_convertible`
* trait uses initialisation from a brace enclosed list, where C++11 prohibits
* narrowing conversions. Now the problem is, that we'll use such traits checks
* to remove such _impossble_ cases from generated trampoline tables or visitor
* double dispatch implementations. Thus, for one we get lots of warnings at that
* point when generating those trampoline tables (at initialisation), while it
* is not clear we'll trigger those cases, and, when we do, we'll get narrowing
* conversions in a context where we're unable to cope with them or protect
* ourselves against spurious conversions.
* What follows is a quick-n-dirty hack to remove such unwanted conversions.
*
* [Bug-63723]: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63723
*/
template<typename SRC, typename TAR>
struct is_narrowingInit
: __or_<__and_<is_unsigned<SRC>, is_signed<TAR>>
,__and_<is_signed<SRC>, is_unsigned<TAR>>
,__and_<is_nonFloat<SRC>, is_floating_point<TAR>>
,__and_<is_floating_point<SRC>, is_nonFloat<TAR>>
,__not_<is_constructible<TAR, SRC>>
>
{ };
template<typename TAR>
struct is_narrowingInit<lib::hash::LuidH, TAR>
: __or_<is_arithmetic<TAR>
,is_floating_point<TAR>
>
{ };
#define TRAIT_IS_NARROWING(_SRC_, _TAR_) \
template<> \
struct is_narrowingInit<_SRC_, _TAR_> \
: std::true_type \
{ };
TRAIT_IS_NARROWING (int64_t, int32_t)
TRAIT_IS_NARROWING (int64_t, int16_t)
TRAIT_IS_NARROWING (int64_t, int8_t)
TRAIT_IS_NARROWING (int64_t, char)
TRAIT_IS_NARROWING (int32_t, int16_t)
TRAIT_IS_NARROWING (int32_t, int8_t)
TRAIT_IS_NARROWING (int32_t, char)
TRAIT_IS_NARROWING (int16_t, int8_t)
TRAIT_IS_NARROWING (int16_t, short)
TRAIT_IS_NARROWING (int16_t, char)
TRAIT_IS_NARROWING (uint64_t, uint32_t)
TRAIT_IS_NARROWING (uint64_t, uint16_t)
TRAIT_IS_NARROWING (uint64_t, uint8_t)
TRAIT_IS_NARROWING (uint32_t, uint16_t)
TRAIT_IS_NARROWING (uint32_t, uint8_t)
TRAIT_IS_NARROWING (uint16_t, uint8_t)
TRAIT_IS_NARROWING (uint16_t, ushort)
TRAIT_IS_NARROWING (double, float)
TRAIT_IS_NARROWING (double, lib::time::TimeValue)
TRAIT_IS_NARROWING (double, lib::time::Duration)
#undef TRAIT_IS_NARROWING
/* ====== generic iteration support ====== */
/** Trait template to detect a type usable immediately as
* "Lumiera Forward Iterator" in a specialised for-each loop
* This is just a heuristic, based on some common properties
* of such iterators; it is enough to distinguish it from an
* STL container, but can certainly be refined.
*/
template<typename T>
class can_IterForEach
{
using Type = Strip<T>::Type;
META_DETECT_NESTED(value_type);
META_DETECT_OPERATOR_DEREF();
META_DETECT_OPERATOR_INC();
public:
enum{ value = std::is_constructible<bool, Type>::value
and HasNested_value_type<Type>::value
and HasOperator_deref<Type>::value
and HasOperator_inc<Type>::value
};
};
/** Trait template to detect a type exposing a »state core« API.
* Such a type can be dressed up as "Lumiera Forward Iterator"
* with the help of lib::IterStateWrapper or lib::IterableDecorator.
* This check is heuristic, based on the presence of function names.
*/
template<typename T>
class is_StateCore
{
using Type = Strip<T>::Type;
META_DETECT_FUNCTION_ARGLESS(checkPoint);
META_DETECT_FUNCTION_ARGLESS(iterNext);
META_DETECT_FUNCTION_ARGLESS(yield);
public:
enum{ value = HasArglessFun_checkPoint<Type>::value
and HasArglessFun_iterNext<Type>::value
and HasArglessFun_yield<Type>::value
};
};
/** Trait template to detect a type usable with the STL for-each loop.
* Basically we're looking for the functions to get the begin/end iterator
*/
template<typename T>
class can_STL_ForEach
{
using Type = Strip<T>::Type;
struct is_iterable
{
META_DETECT_NESTED(iterator);
META_DETECT_FUNCTION(typename X::iterator, begin,(void));
META_DETECT_FUNCTION(typename X::iterator, end ,(void));
enum { value = HasNested_iterator<Type>::value
and HasFunSig_begin<Type>::value
and HasFunSig_end<Type>::value
};
};
struct is_noexcept_iterable
{
META_DETECT_NESTED(iterator);
META_DETECT_FUNCTION(typename X::iterator, begin,(void) noexcept);
META_DETECT_FUNCTION(typename X::iterator, end ,(void) noexcept);
enum { value = HasNested_iterator<Type>::value
and HasFunSig_begin<Type>::value
and HasFunSig_end<Type>::value
};
};
struct is_const_iterable
{
META_DETECT_NESTED(const_iterator);
META_DETECT_FUNCTION(typename X::const_iterator, begin,(void) const);
META_DETECT_FUNCTION(typename X::const_iterator, end ,(void) const);
enum { value = HasNested_const_iterator<Type>::value
and HasFunSig_begin<Type>::value
and HasFunSig_end<Type>::value
};
};
struct is_const_noexcept_iterable
{
META_DETECT_NESTED(const_iterator);
META_DETECT_FUNCTION(typename X::const_iterator, begin,(void) const noexcept);
META_DETECT_FUNCTION(typename X::const_iterator, end ,(void) const noexcept);
enum { value = HasNested_const_iterator<Type>::value
and HasFunSig_begin<Type>::value
and HasFunSig_end<Type>::value
};
};
public:
enum { value = is_iterable::value
or is_const_iterable::value
or is_noexcept_iterable::value
or is_const_noexcept_iterable::value
};
};
/** Trait template to detect a type also supporting STL-style backwards iteration */
template<typename T>
class can_STL_backIteration
{
using Type = Strip<T>::Type;
struct is_backIterable
{
META_DETECT_NESTED(reverse_iterator);
META_DETECT_FUNCTION(typename X::reverse_iterator, rbegin,(void));
META_DETECT_FUNCTION(typename X::reverse_iterator, rend ,(void));
enum { value = HasNested_reverse_iterator<Type>::value
and HasFunSig_rbegin<Type>::value
and HasFunSig_rend<Type>::value
};
};
struct is_noexcept_backIterable
{
META_DETECT_NESTED(reverse_iterator);
META_DETECT_FUNCTION(typename X::reverse_iterator, rbegin,(void) noexcept);
META_DETECT_FUNCTION(typename X::reverse_iterator, rend ,(void) noexcept);
enum { value = HasNested_reverse_iterator<Type>::value
and HasFunSig_rbegin<Type>::value
and HasFunSig_rend<Type>::value
};
};
struct is_const_backIterable
{
META_DETECT_NESTED(const_reverse_iterator);
META_DETECT_FUNCTION(typename X::const_reverse_iterator, rbegin,(void) const);
META_DETECT_FUNCTION(typename X::const_reverse_iterator, rend ,(void) const);
enum { value = HasNested_const_reverse_iterator<Type>::value
and HasFunSig_rbegin<Type>::value
and HasFunSig_rend<Type>::value
};
};
struct is_const_noexcept_backIterable
{
META_DETECT_NESTED(const_reverse_iterator);
META_DETECT_FUNCTION(typename X::const_reverse_iterator, rbegin,(void) const noexcept);
META_DETECT_FUNCTION(typename X::const_reverse_iterator, rend ,(void) const noexcept);
enum { value = HasNested_const_reverse_iterator<Type>::value
and HasFunSig_rbegin<Type>::value
and HasFunSig_rend<Type>::value
};
};
public:
enum { value = is_backIterable::value
or is_const_backIterable::value
or is_noexcept_backIterable::value
or is_const_noexcept_backIterable::value
};
};
}} // namespace lib::meta
#endif