10

TL;DR: I am looking for a C++14 equivalent of the following C++20 MWE:

template<int sz>
struct bits {
  int v; // note explicit(expr) below
  explicit(sz > 1) operator bool() const { return bool(v); }
};

int main() {
  bool c = bits<1>{1}; // Should work
  bool d = bits<3>{1}; // Should fail
}

Context:

We have a C++ class bits<sz> representing bitvectors of length sz. Conversion to bool used to be implicit for all sz, but this proved to be error-prone, so we changed operator bool() to be explicit.

However, 1-bit bitvectors are (in our context) almost-completely equivalent to Booleans, so it would be desirable for operator bool() to be implicit when sz == 1.

This can be achieved with explicit(sz > 1) in C++20, but we are targeting C++14.

I tried to overload the operator for sz == 1, but it seems that the explicit qualifier applies to it as well: the following does not work.

template<int sz>
struct bits {
  int v;
  explicit operator bool() const { return bool(v); }
};

template<> bits<1>::operator bool() const {
  return bool(v);
}

int main() {
  bool c = bits<1>{1}; // Fails: "No viable conversion"
}

Hence the question: How can I specify in C++14 that operator bool() should be explicit only for sz > 1?

I'm including some background below for curious readers.

Background:

This problem came up in the context of an embedded domain-specific language in C++. One of the business requirements is that operator== returns a bit<1>, not a bool. This is working smoothly with GNU's libstdc++, but we're running into trouble with that requirement on macOS because libstdc++ there implements operator== on std::array using the version of std::equal that takes a predicate, and implements that predicate using a struct whose operator() returns bool with body a == b (which in our case returns a bits<1>, causing a conversion error).

To make it concrete for curious readers, the following program compiles fine on GNU, but not on macOS, because of the way operator== on std::array is implemented:

#include <array>

struct S { explicit operator bool() const { return true; } };

struct T {};
S operator==(T, T) { return S(); }

int main() {
  std::array<T, 1> arr = { T() };
  return arr == arr;
}

That's because deep down in the implementation of == on arrays GNU libstdc++ has a test if (!(*it1 == *it2)), which invokes the explicit operator bool() on S without trouble, where as on macOS the library uses if (!__pred(*it1, *it2)) with __pred roughly equivalent to bool __pred(S a, S b) { return a == b; }, which doesn't typecheck.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
Clément
  • 12,299
  • 15
  • 75
  • 115

1 Answers1

13

Yes. You can SFINAE the conversion operator:

#include <type_traits>

template<int sz>
struct bits {
  int v;
  explicit operator bool() const { return bool(v); }

  template <int S = sz>
  operator std::enable_if_t<S == 1, bool> () const { return bool(v);}
};

Check it on godbolt

int main()
{
  bool c1 = bits<1>{1};                    // Ok
  bool c2 = bits<3>{1};                    // error cannot convert
  bool c3 = static_cast<bool>(bits<3>{1}); // OK
}
bolov
  • 72,283
  • 15
  • 145
  • 224