Don't require relops on variant alternatives to all return the same
authorEric Fiselier <eric@efcs.ca>
Wed, 19 Sep 2018 17:53:21 +0000 (17:53 +0000)
committerEric Fiselier <eric@efcs.ca>
Wed, 19 Sep 2018 17:53:21 +0000 (17:53 +0000)
type.

Libc++ correctly asserts that a set of visitors for a variant all
return the same type. However, we use the visitation machinary to
perform relational operations. This causes a static assertion when
some of the alternatives relops return a UDT which is implicitly
convertible to bool instead of 'bool' exactly.

llvm-svn: 342560

libcxx/include/variant
libcxx/test/std/utilities/variant/variant.relops/relops.pass.cpp
libcxx/test/std/utilities/variant/variant.relops/relops_bool_conv.fail.cpp [new file with mode: 0644]

index 6a52356..f9505bf 100644 (file)
@@ -1438,6 +1438,16 @@ get_if(const variant<_Types...>* __v) noexcept {
   return _VSTD::get_if<__find_exactly_one_t<_Tp, _Types...>::value>(__v);
 }
 
+template <class _Operator>
+struct __convert_to_bool {
+  template <class _T1, class _T2>
+  _LIBCPP_INLINE_VISIBILITY constexpr bool operator()(_T1 && __t1, _T2&& __t2) const {
+    static_assert(std::is_convertible<decltype(_Operator{}(_VSTD::forward<_T1>(__t1), _VSTD::forward<_T2>(__t2))), bool>::value,
+        "the relational operator does not return a type which is implicitly convertible to bool");
+    return _Operator{}(_VSTD::forward<_T1>(__t1), _VSTD::forward<_T2>(__t2));
+  }
+};
+
 template <class... _Types>
 inline _LIBCPP_INLINE_VISIBILITY
 constexpr bool operator==(const variant<_Types...>& __lhs,
@@ -1445,7 +1455,7 @@ constexpr bool operator==(const variant<_Types...>& __lhs,
   using __variant_detail::__visitation::__variant;
   if (__lhs.index() != __rhs.index()) return false;
   if (__lhs.valueless_by_exception()) return true;
-  return __variant::__visit_value_at(__lhs.index(), equal_to<>{}, __lhs, __rhs);
+  return __variant::__visit_value_at(__lhs.index(), __convert_to_bool<equal_to<>>{}, __lhs, __rhs);
 }
 
 template <class... _Types>
@@ -1456,7 +1466,7 @@ constexpr bool operator!=(const variant<_Types...>& __lhs,
   if (__lhs.index() != __rhs.index()) return true;
   if (__lhs.valueless_by_exception()) return false;
   return __variant::__visit_value_at(
-      __lhs.index(), not_equal_to<>{}, __lhs, __rhs);
+      __lhs.index(), __convert_to_bool<not_equal_to<>>{}, __lhs, __rhs);
 }
 
 template <class... _Types>
@@ -1468,7 +1478,7 @@ constexpr bool operator<(const variant<_Types...>& __lhs,
   if (__lhs.valueless_by_exception()) return true;
   if (__lhs.index() < __rhs.index()) return true;
   if (__lhs.index() > __rhs.index()) return false;
-  return __variant::__visit_value_at(__lhs.index(), less<>{}, __lhs, __rhs);
+  return __variant::__visit_value_at(__lhs.index(), __convert_to_bool<less<>>{}, __lhs, __rhs);
 }
 
 template <class... _Types>
@@ -1480,7 +1490,7 @@ constexpr bool operator>(const variant<_Types...>& __lhs,
   if (__rhs.valueless_by_exception()) return true;
   if (__lhs.index() > __rhs.index()) return true;
   if (__lhs.index() < __rhs.index()) return false;
-  return __variant::__visit_value_at(__lhs.index(), greater<>{}, __lhs, __rhs);
+  return __variant::__visit_value_at(__lhs.index(), __convert_to_bool<greater<>>{}, __lhs, __rhs);
 }
 
 template <class... _Types>
@@ -1493,7 +1503,7 @@ constexpr bool operator<=(const variant<_Types...>& __lhs,
   if (__lhs.index() < __rhs.index()) return true;
   if (__lhs.index() > __rhs.index()) return false;
   return __variant::__visit_value_at(
-      __lhs.index(), less_equal<>{}, __lhs, __rhs);
+      __lhs.index(), __convert_to_bool<less_equal<>>{}, __lhs, __rhs);
 }
 
 template <class... _Types>
@@ -1506,7 +1516,7 @@ constexpr bool operator>=(const variant<_Types...>& __lhs,
   if (__lhs.index() > __rhs.index()) return true;
   if (__lhs.index() < __rhs.index()) return false;
   return __variant::__visit_value_at(
-      __lhs.index(), greater_equal<>{}, __lhs, __rhs);
+      __lhs.index(), __convert_to_bool<greater_equal<>>{}, __lhs, __rhs);
 }
 
 template <class _Visitor, class... _Vs>
index 4337b4b..41a9535 100644 (file)
@@ -85,43 +85,79 @@ template <class Variant> void makeEmpty(Variant &v) {
 }
 #endif // TEST_HAS_NO_EXCEPTIONS
 
-void test_equality() {
+struct MyBool {
+  bool value;
+  constexpr explicit MyBool(bool v) : value(v) {}
+  constexpr operator bool() const noexcept { return value; }
+};
+
+struct ComparesToMyBool {
+  int value = 0;
+};
+inline constexpr MyBool operator==(const ComparesToMyBool& LHS, const ComparesToMyBool& RHS) noexcept {
+  return MyBool(LHS.value == RHS.value);
+}
+inline constexpr MyBool operator!=(const ComparesToMyBool& LHS, const ComparesToMyBool& RHS) noexcept {
+  return MyBool(LHS.value != RHS.value);
+}
+inline constexpr MyBool operator<(const ComparesToMyBool& LHS, const ComparesToMyBool& RHS) noexcept {
+  return MyBool(LHS.value < RHS.value);
+}
+inline constexpr MyBool operator<=(const ComparesToMyBool& LHS, const ComparesToMyBool& RHS) noexcept {
+  return MyBool(LHS.value <= RHS.value);
+}
+inline constexpr MyBool operator>(const ComparesToMyBool& LHS, const ComparesToMyBool& RHS) noexcept {
+  return MyBool(LHS.value > RHS.value);
+}
+inline constexpr MyBool operator>=(const ComparesToMyBool& LHS, const ComparesToMyBool& RHS) noexcept {
+  return MyBool(LHS.value >= RHS.value);
+}
+
+template <class T1, class T2>
+void test_equality_basic() {
   {
-    using V = std::variant<int, long>;
-    constexpr V v1(42);
-    constexpr V v2(42);
+    using V = std::variant<T1, T2>;
+    constexpr V v1(std::in_place_index<0>, T1{42});
+    constexpr V v2(std::in_place_index<0>, T1{42});
     static_assert(v1 == v2, "");
     static_assert(v2 == v1, "");
     static_assert(!(v1 != v2), "");
     static_assert(!(v2 != v1), "");
   }
   {
-    using V = std::variant<int, long>;
-    constexpr V v1(42);
-    constexpr V v2(43);
+    using V = std::variant<T1, T2>;
+    constexpr V v1(std::in_place_index<0>, T1{42});
+    constexpr V v2(std::in_place_index<0>, T1{43});
     static_assert(!(v1 == v2), "");
     static_assert(!(v2 == v1), "");
     static_assert(v1 != v2, "");
     static_assert(v2 != v1, "");
   }
   {
-    using V = std::variant<int, long>;
-    constexpr V v1(42);
-    constexpr V v2(42l);
+    using V = std::variant<T1, T2>;
+    constexpr V v1(std::in_place_index<0>, T1{42});
+    constexpr V v2(std::in_place_index<1>, T2{42});
     static_assert(!(v1 == v2), "");
     static_assert(!(v2 == v1), "");
     static_assert(v1 != v2, "");
     static_assert(v2 != v1, "");
   }
   {
-    using V = std::variant<int, long>;
-    constexpr V v1(42l);
-    constexpr V v2(42l);
+    using V = std::variant<T1, T2>;
+    constexpr V v1(std::in_place_index<1>, T2{42});
+    constexpr V v2(std::in_place_index<1>, T2{42});
     static_assert(v1 == v2, "");
     static_assert(v2 == v1, "");
     static_assert(!(v1 != v2), "");
     static_assert(!(v2 != v1), "");
   }
+}
+
+void test_equality() {
+  test_equality_basic<int, long>();
+  test_equality_basic<ComparesToMyBool, int>();
+  test_equality_basic<int, ComparesToMyBool>();
+  test_equality_basic<ComparesToMyBool, ComparesToMyBool>();
 #ifndef TEST_HAS_NO_EXCEPTIONS
   {
     using V = std::variant<int, MakeEmptyT>;
@@ -160,41 +196,54 @@ void test_equality() {
 template <class Var>
 constexpr bool test_less(const Var &l, const Var &r, bool expect_less,
                          bool expect_greater) {
+  static_assert(std::is_same_v<decltype(l < r), bool>, "");
+  static_assert(std::is_same_v<decltype(l <= r), bool>, "");
+  static_assert(std::is_same_v<decltype(l > r), bool>, "");
+  static_assert(std::is_same_v<decltype(l >= r), bool>, "");
+
   return ((l < r) == expect_less) && (!(l >= r) == expect_less) &&
          ((l > r) == expect_greater) && (!(l <= r) == expect_greater);
 }
 
-void test_relational() {
+template <class T1, class T2>
+void test_relational_basic() {
   { // same index, same value
-    using V = std::variant<int, long>;
-    constexpr V v1(1);
-    constexpr V v2(1);
+    using V = std::variant<T1, T2>;
+    constexpr V v1(std::in_place_index<0>, T1{1});
+    constexpr V v2(std::in_place_index<0>, T1{1});
     static_assert(test_less(v1, v2, false, false), "");
   }
   { // same index, value < other_value
-    using V = std::variant<int, long>;
-    constexpr V v1(0);
-    constexpr V v2(1);
+    using V = std::variant<T1, T2>;
+    constexpr V v1(std::in_place_index<0>, T1{0});
+    constexpr V v2(std::in_place_index<0>, T1{1});
     static_assert(test_less(v1, v2, true, false), "");
   }
   { // same index, value > other_value
-    using V = std::variant<int, long>;
-    constexpr V v1(1);
-    constexpr V v2(0);
+    using V = std::variant<T1, T2>;
+    constexpr V v1(std::in_place_index<0>, T1{1});
+    constexpr V v2(std::in_place_index<0>, T1{0});
     static_assert(test_less(v1, v2, false, true), "");
   }
   { // LHS.index() < RHS.index()
-    using V = std::variant<int, long>;
-    constexpr V v1(0);
-    constexpr V v2(0l);
+    using V = std::variant<T1, T2>;
+    constexpr V v1(std::in_place_index<0>, T1{0});
+    constexpr V v2(std::in_place_index<1>, T2{0});
     static_assert(test_less(v1, v2, true, false), "");
   }
   { // LHS.index() > RHS.index()
-    using V = std::variant<int, long>;
-    constexpr V v1(0l);
-    constexpr V v2(0);
+    using V = std::variant<T1, T2>;
+    constexpr V v1(std::in_place_index<1>, T2{0});
+    constexpr V v2(std::in_place_index<0>, T1{0});
     static_assert(test_less(v1, v2, false, true), "");
   }
+}
+
+void test_relational() {
+  test_relational_basic<int, long>();
+  test_relational_basic<ComparesToMyBool, int>();
+  test_relational_basic<int, ComparesToMyBool>();
+  test_relational_basic<ComparesToMyBool, ComparesToMyBool>();
 #ifndef TEST_HAS_NO_EXCEPTIONS
   { // LHS.index() < RHS.index(), RHS is empty
     using V = std::variant<int, MakeEmptyT>;
diff --git a/libcxx/test/std/utilities/variant/variant.relops/relops_bool_conv.fail.cpp b/libcxx/test/std/utilities/variant/variant.relops/relops_bool_conv.fail.cpp
new file mode 100644 (file)
index 0000000..c0c296c
--- /dev/null
@@ -0,0 +1,88 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is dual licensed under the MIT and the University of Illinois Open
+// Source Licenses. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++98, c++03, c++11, c++14
+
+// <variant>
+
+// template <class ...Types>
+// constexpr bool
+// operator==(variant<Types...> const&, variant<Types...> const&) noexcept;
+//
+// template <class ...Types>
+// constexpr bool
+// operator!=(variant<Types...> const&, variant<Types...> const&) noexcept;
+//
+// template <class ...Types>
+// constexpr bool
+// operator<(variant<Types...> const&, variant<Types...> const&) noexcept;
+//
+// template <class ...Types>
+// constexpr bool
+// operator>(variant<Types...> const&, variant<Types...> const&) noexcept;
+//
+// template <class ...Types>
+// constexpr bool
+// operator<=(variant<Types...> const&, variant<Types...> const&) noexcept;
+//
+// template <class ...Types>
+// constexpr bool
+// operator>=(variant<Types...> const&, variant<Types...> const&) noexcept;
+
+#include <cassert>
+#include <type_traits>
+#include <utility>
+#include <variant>
+
+#include "test_macros.h"
+
+
+struct MyBoolExplicit {
+  bool value;
+  constexpr explicit MyBoolExplicit(bool v) : value(v) {}
+  constexpr explicit operator bool() const noexcept { return value; }
+};
+
+struct ComparesToMyBoolExplicit {
+  int value = 0;
+};
+inline constexpr MyBoolExplicit operator==(const ComparesToMyBoolExplicit& LHS, const ComparesToMyBoolExplicit& RHS) noexcept {
+  return MyBoolExplicit(LHS.value == RHS.value);
+}
+inline constexpr MyBoolExplicit operator!=(const ComparesToMyBoolExplicit& LHS, const ComparesToMyBoolExplicit& RHS) noexcept {
+  return MyBoolExplicit(LHS.value != RHS.value);
+}
+inline constexpr MyBoolExplicit operator<(const ComparesToMyBoolExplicit& LHS, const ComparesToMyBoolExplicit& RHS) noexcept {
+  return MyBoolExplicit(LHS.value < RHS.value);
+}
+inline constexpr MyBoolExplicit operator<=(const ComparesToMyBoolExplicit& LHS, const ComparesToMyBoolExplicit& RHS) noexcept {
+  return MyBoolExplicit(LHS.value <= RHS.value);
+}
+inline constexpr MyBoolExplicit operator>(const ComparesToMyBoolExplicit& LHS, const ComparesToMyBoolExplicit& RHS) noexcept {
+  return MyBoolExplicit(LHS.value > RHS.value);
+}
+inline constexpr MyBoolExplicit operator>=(const ComparesToMyBoolExplicit& LHS, const ComparesToMyBoolExplicit& RHS) noexcept {
+  return MyBoolExplicit(LHS.value >= RHS.value);
+}
+
+
+int main() {
+  using V = std::variant<int, ComparesToMyBoolExplicit>;
+  V v1(42);
+  V v2(101);
+  // expected-error-re@variant:* 6 {{static_assert failed due to requirement 'std::is_convertible{{[^']+}}' "the relational operator does not return a type which is implicitly convertible to bool"}}
+  // expected-error@variant:* 6 {{no viable conversion}}
+  (void)(v1 == v2); // expected-note {{here}}
+  (void)(v1 != v2); // expected-note {{here}}
+  (void)(v1 < v2); // expected-note {{here}}
+  (void)(v1 <= v2); // expected-note {{here}}
+  (void)(v1 > v2); // expected-note {{here}}
+  (void)(v1 >= v2); // expected-note {{here}}
+}