https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71899

--- Comment #1 from Daniel Krügler <daniel.kruegler at googlemail dot com> ---
I have now a working implementation available, my minimum requirement set is
summarized by the following trait definition:

//--------------------------------
  // Utility to detect BooleanTestable types (NB: LWG 2114).

  template<typename _Tp, bool = __is_referenceable<_Tp>::value>
    struct __is_boolean_testable_impl;

  template<typename _Tp>
    struct __is_boolean_testable_impl<_Tp, false>
    : public false_type { };

  template<typename _Tp>
    struct __is_boolean_testable_impl<_Tp, true>
    : public __and_<is_convertible<const _Tp&, bool>, 
                    is_constructible<bool, const _Tp&>>
    { };

  template<typename _Tp>
    struct __is_boolean_testable
    : public __is_boolean_testable_impl<_Tp> { };
//--------------------------------

During my work on this I wanted to stretch its limits and extended the
definition to cover basically all statically testable expressions as currently
specified by the BooleanTestable requirements [booleantestable] in LWG 2743 as
follows:

//--------------------------------
  // Utility to detect BooleanTestable types (NB: LWG 2114).

  template<typename _Tp, bool = __is_referenceable<_Tp>::value>
    struct __is_boolean_testable_impl;

  template<typename _Tp>
    struct __is_boolean_testable_impl<_Tp, false>
    : public false_type { };

#ifdef EXT_BOOLEAN_TESTABLE_OPS
  struct __do_is_boolean_testable_ext_impl
  {
    struct __bt
    {
      operator bool() const;
    };

    template<typename _Tp, typename _Rt0
             = decltype(!declval<_Tp>()), 
             typename _Rt1
             = decltype(declval<_Tp>() && declval<const __bt&>()), 
             typename _Rt2
             = decltype(declval<_Tp>() || declval<const __bt&>()), 
             typename _Rt3
             = decltype(declval<const __bt&>() && declval<_Tp>()), 
             typename _Rt4
             = decltype(declval<const __bt&>() || declval<_Tp>()), 
             typename _Rt5
             = decltype(declval<_Tp>() && declval<_Tp>()), 
             typename _Rt6
             = decltype(declval<_Tp>() || declval<_Tp>()),
             typename = typename enable_if<__and_<
               is_same<_Rt0, bool>,
               is_same<_Rt1, bool>, is_same<_Rt2, bool>,
               is_same<_Rt3, bool>, is_same<_Rt4, bool>,
               is_same<_Rt5, bool>, is_same<_Rt6, bool>
             >::value>::type>
      static true_type __test(int);

    template<typename>
      static false_type __test(...);
  };

  template<typename _Tp>
    struct __is_boolean_testable_ext
    : public __do_is_boolean_testable_ext_impl
    {
      typedef decltype(__test<_Tp>(0)) type;
    };

  template<typename _Tp>
    struct __is_boolean_testable_ext_safe
    : public __is_boolean_testable_ext<_Tp>::type
    { };
#endif

  template<typename _Tp>
    struct __is_boolean_testable_impl<_Tp, true>
    : public __and_<is_convertible<const _Tp&, bool>, 
                    is_constructible<bool, const _Tp&>
#ifdef EXT_BOOLEAN_TESTABLE_OPS
                    ,
                    __is_boolean_testable_ext_safe<const _Tp&>
#endif
                   >
    { };

  template<typename _Tp>
    struct __is_boolean_testable
    : public __is_boolean_testable_impl<_Tp> { };
//--------------------------------

Please note that the extended test added the slightly stricter requirement
is_same<_Rt0, bool> instead of __is_boolean_testable<_Rt0>, because the latter
would have lead to an indefinite recursion. But even this much stricter (and
more complex) __is_boolean_testable definition (i.e. with
EXT_BOOLEAN_TESTABLE_OPS being defined as shown above) resulted in an accepted
test case: 

//---------------------------------
#include <type_traits>
#include <bitset>
#include <vector>

struct BooleanLike
{
  operator bool() const;
};

struct NotBooleanLike1
{
  explicit operator bool() const;
};

struct NotBooleanLike2
{
  using Bool = int;
  explicit operator bool() const = delete;
  operator Bool() const;
};

struct NotBooleanLike3
{
  operator bool();
};

void test01()
{
  using std::__is_boolean_testable;
  // Positive tests.
  static_assert(__is_boolean_testable<bool>::value, "");
  static_assert(__is_boolean_testable<bool&>::value, "");
  static_assert(__is_boolean_testable<const bool&>::value, "");
  static_assert(__is_boolean_testable<std::true_type>::value, "");
  static_assert(__is_boolean_testable<std::false_type>::value, "");
  static_assert(__is_boolean_testable<BooleanLike>::value, "");
  static_assert(__is_boolean_testable<std::bitset<1>::reference>::value, "");
  static_assert(__is_boolean_testable<std::bitset<24>::reference>::value, "");
  static_assert(__is_boolean_testable<std::vector<bool>::reference>::value,
"");

  // Negative tests.
  static_assert(!__is_boolean_testable<void>::value, "");
  static_assert(!__is_boolean_testable<NotBooleanLike1>::value, "");
  static_assert(!__is_boolean_testable<NotBooleanLike2>::value, "");
  static_assert(!__is_boolean_testable<NotBooleanLike3>::value, "");
}
//---------------------------------

I would therefore like to ask which form of the trait would be considered more
welcome as internal utility for libstdc++?

Reply via email to