#ifndef INCLUDED_BOBCAT_TYPETRAIT_
#define INCLUDED_BOBCAT_TYPETRAIT_

#include <type_traits>

namespace FBB
{

template <typename T>
class TypeTrait
{
    template <typename ClassType>
    static double fun(void (ClassType::*)());

    template <typename NonClassType>
    static char fun(...);

    template <typename U> struct Type
    {
        enum { isClass = sizeof(fun<U>(0)) == sizeof(double) };
        enum { isConst   = false };
        enum { isPointer = false };
        enum { isR_Ref   = false };
        enum { isRef     = false };

        using Plain =  U;
    };
    template <typename U> struct Type<U const>
    {
        enum { isClass = sizeof(fun<U>(0)) == sizeof(double) };
        enum { isPointer = false };
        enum { isConst   = true };
        enum { isRef     = false };
        enum { isR_Ref   = false };

        using Plain =  U;
    };
    template <typename U> struct Type<U *>
    {
        enum { isClass = false };
        enum { isPointer = true };
        enum { isConst   = false };
        enum { isRef     = false };
        enum { isR_Ref   = false };

        using Plain =  U;
    };
    template <typename U> struct Type<U const *>
    {
        enum { isClass = false };
        enum { isPointer = true };
        enum { isConst   = true };
        enum { isRef     = false };
        enum { isR_Ref   = false };

        using Plain =  U;
    };
    template <typename U> struct Type<U &>
    {
        enum { isClass = sizeof(fun<U>(0)) == sizeof(double) };
        enum { isPointer = false };
        enum { isConst   = false };
        enum { isRef     = true };
        enum { isR_Ref   = false };

        using Plain =  U;
    };
    template <typename U> struct Type<U const &>
    {
        enum { isClass = sizeof(fun<U>(0)) == sizeof(double) };
        enum { isPointer = false };
        enum { isConst   = true };
        enum { isRef     = true };
        enum { isR_Ref   = false };

        using Plain =  U;
    };
    template <typename U> struct Type<U &&>
    {
        enum { isClass = sizeof(fun<U>(0)) == sizeof(double) };
        enum { isPointer = false };
        enum { isConst   = false };
        enum { isRef     = false };
        enum { isR_Ref   = true  };

        using Plain =  U;
    };
    template <typename U> struct Type<U const &&>
    {
        enum { isClass = sizeof(fun<U>(0)) == sizeof(double) };
        enum { isPointer = false };
        enum { isConst   = true };
        enum { isRef     = false };
        enum { isR_Ref   = true  };

        using Plain =  U;
    };

    public:
        enum { isClass = Type<T>::isClass };
        enum { isPointer = Type<T>::isPointer };
        enum { isConst   = Type<T>::isConst };
        enum { isRef     = Type<T>::isRef };
        enum { isR_Ref   = Type<T>::isR_Ref };

        using Plain =  typename Type<T>::Plain;
};

template <typename L, typename R>
class LpromotesR
{
    struct Char2
    {
        char array[2];
    };
    static R const &makeR();
    static char test(L const &);
    static Char2 test(...);

    public:
        LpromotesR() = delete;
        enum { yes = sizeof(test(makeR())) == sizeof(char) };
};

template <typename LHS, typename RHS>
struct Use
{
    using type =  
        typename std::conditional< LpromotesR<LHS, RHS>::yes, LHS, RHS>::type;
};


} // FBB

#endif
