Bug 71899 - An internal BooleanTestable trait should be provided
Summary: An internal BooleanTestable trait should be provided
Status: RESOLVED WONTFIX
Alias: None
Product: gcc
Classification: Unclassified
Component: libstdc++ (show other bugs)
Version: 6.0
: P3 enhancement
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2016-07-15 20:33 UTC by Daniel Krügler
Modified: 2020-12-10 16:33 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2016-07-15 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Daniel Krügler 2016-07-15 20:33:58 UTC
I suggest to provide an internal type trait that corresponds to the currently discussed BooleanTestable requirement set as described in LWG 2743:

http://cplusplus.github.io/LWG/lwg-active.html#2743

This trait could be used in SFINAE-constraints such as for the recently added comparison functions of std::optional or as argument of static_assert for the existing comparison functions of std::tuple and possibly other places as well.

In the absence of any better name, I suggest to introduce a type trait named __is_boolean_testable in header <type_traits>.

Given that LWG 2743 is not resolved yet, I recommend to start with a very weak set of requirements for some type T, e.g. the logical AND of

std::is_convertible<const T&, bool>::value
std::is_constructible<bool, const T&>::value

I'm willing to provide this contribution.
Comment 1 Daniel Krügler 2016-07-16 16:39:13 UTC
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++?
Comment 2 Ville Voutilainen 2016-07-18 16:56:27 UTC
I dislike the #ifdef parts. It seems to me both modes of operation
could be provided without resorting to the preprocessor. I'm also
not a fan of the name boolean_testable - perhaps boolean_like would
be better? The rationale being that there are "boolean testable" types
that work in cases where a contextual conversion to bool is performed,
but those types are otherwise not "boolean-like". Sure, we could
go with boolean_testable and potentially contextually_boolean_testable
or some such. It seems to me that both of those facilities are useful.

I do wonder why we even bother supporting e.g. comparison operators that
don't just return bool, although that's in some ways a separate matter.
Comment 3 Daniel Krügler 2016-07-18 17:26:07 UTC
(In reply to Ville Voutilainen from comment #2)
> I dislike the #ifdef parts.

I'm sorry for my misleading proposal. My extended proposal is not suggesting to add this macro. I was using this macro solely for experimentation purposes. My intended question was solely whether we would prefer to have the more reduced set of requirements as defined in my original proposal or the more complete requirement set that would result if you consider the macro EXT_BOOLEAN_TESTABLE_OPS defined (but without the #ifdef logic of that macro). This more complete set corresponds more strictly to the current BooleanTestable requirement set.
Comment 4 Daniel Krügler 2016-07-19 19:16:44 UTC
(In reply to Ville Voutilainen from comment #2)
[..]
> I'm also not a fan of the name boolean_testable

Note that no-one yet has made an improved name suggestion for this thingee that is discussed in LWG 2743. Unless this happens, I think that the corresponding trait should match this name and should match the intention of what is tested here.

> - perhaps boolean_like would
> be better? The rationale being that there are "boolean testable" types
> that work in cases where a contextual conversion to bool is performed,
> but those types are otherwise not "boolean-like". 

If you believe so, please make a corresponding comment to LWG 2743 and convince people for accepting this revised name.
Comment 5 Jonathan Wakely 2020-12-10 12:16:27 UTC
LWG 2743 seems to be the wrong issue, I think https://wg21.link/lwg2114 is the right one.
Comment 6 Daniel Krügler 2020-12-10 16:17:29 UTC
(In reply to Jonathan Wakely from comment #5)
> LWG 2743 seems to be the wrong issue, I think https://wg21.link/lwg2114 is
> the right one.

Ah yes, this was an unintended mislinking on my side. Feel free to close this issue, the resolution approach is now different already and it is likely the libraries will already implement something like boolean-testable-impl anyway (https://wg21.link/p1964r2).
Comment 7 Jonathan Wakely 2020-12-10 16:33:11 UTC
Yep, added in c5e1c1d3aba39e960cc5fb0dcd77e447e5dee7eb

OK, closing, thanks.