aboutsummaryrefslogtreecommitdiff
path: root/llvm/unittests/ADT/OptionalTest.cpp
diff options
context:
space:
mode:
authorJames Player <jplayer@nvidia.com>2021-01-16 09:34:20 -0500
committerAlexandre Ganea <alexandre.ganea@ubisoft.com>2021-01-16 09:37:04 -0500
commit25c1578a46ff93f920b7ad4e3057465902ced8f5 (patch)
treed95473e0956c6beb821db013d1e2b4efd7b5b3ea /llvm/unittests/ADT/OptionalTest.cpp
parentb765eaf9a617bd3da30f47ece731b33593929885 (diff)
downloadllvm-25c1578a46ff93f920b7ad4e3057465902ced8f5.zip
llvm-25c1578a46ff93f920b7ad4e3057465902ced8f5.tar.gz
llvm-25c1578a46ff93f920b7ad4e3057465902ced8f5.tar.bz2
Fix llvm::Optional build breaks in MSVC using std::is_trivially_copyable
Current code breaks this version of MSVC due to a mismatch between `std::is_trivially_copyable` and `llvm::is_trivially_copyable` for `std::pair` instantiations. Hence I was attempting to use `std::is_trivially_copyable` to set `llvm::is_trivially_copyable<T>::value`. I spent some time root causing an `llvm::Optional` build error on MSVC 16.8.3 related to the change described above: ``` 62>C:\src\ocg_llvm\llvm-project\llvm\include\llvm/ADT/BreadthFirstIterator.h(96,12): error C2280: 'llvm::Optional<std::pair<std::pair<unsigned int,llvm::Graph<4>::NodeSubset> *,llvm::Optional<llvm::Graph<4>::ChildIterator>>> &llvm::Optional<std::pair<std::pair<unsigned int,llvm::Graph<4>::NodeSubset> *,llvm::Optional<llvm::Graph<4>::ChildIterator>>>::operator =(const llvm::Optional<std::pair<std::pair<unsigned int,llvm::Graph<4>::NodeSubset> *,llvm::Optional<llvm::Graph<4>::ChildIterator>>> &)': attempting to reference a deleted function (compiling source file C:\src\ocg_llvm\llvm-project\llvm\unittests\ADT\BreadthFirstIteratorTest.cpp) ... ``` The "trivial" specialization of `optional_detail::OptionalStorage` assumes that the value type is trivially copy constructible and trivially copy assignable. The specialization is invoked based on a check of `is_trivially_copyable` alone, which does not imply both `is_trivially_copy_assignable` and `is_trivially_copy_constructible` are true. [[ https://en.cppreference.com/w/cpp/named_req/TriviallyCopyable | According to the spec ]], a deleted assignment operator does not make `is_trivially_copyable` false. So I think all these properties need to be checked explicitly in order to specialize `OptionalStorage` to the "trivial" version: ``` /// Storage for any type. template <typename T, bool = std::is_trivially_copy_constructible<T>::value && std::is_trivially_copy_assignable<T>::value> class OptionalStorage { ``` Above fixed my build break in MSVC, but I think we need to explicitly check `is_trivially_copy_constructible` too since it might be possible the copy constructor is deleted. Also would be ideal to move over to `std::is_trivially_copyable` instead of the `llvm` namespace verson. Reviewed By: dblaikie Differential Revision: https://reviews.llvm.org/D93510
Diffstat (limited to 'llvm/unittests/ADT/OptionalTest.cpp')
-rw-r--r--llvm/unittests/ADT/OptionalTest.cpp138
1 files changed, 138 insertions, 0 deletions
diff --git a/llvm/unittests/ADT/OptionalTest.cpp b/llvm/unittests/ADT/OptionalTest.cpp
index c7fa796..d40e212 100644
--- a/llvm/unittests/ADT/OptionalTest.cpp
+++ b/llvm/unittests/ADT/OptionalTest.cpp
@@ -8,6 +8,7 @@
#include "llvm/ADT/Optional.h"
#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringMap.h"
#include "llvm/Support/raw_ostream.h"
#include "gtest/gtest-spi.h"
#include "gtest/gtest.h"
@@ -390,6 +391,143 @@ TEST(OptionalTest, ImmovableEmplace) {
EXPECT_EQ(0u, Immovable::Destructions);
}
+// Craft a class which is_trivially_copyable, but not
+// is_trivially_copy_constructible.
+struct NonTCopy {
+ NonTCopy() = default;
+
+ // Delete the volatile copy constructor to engage the "rule of 3" and delete
+ // any unspecified copy assignment or constructor.
+ NonTCopy(volatile NonTCopy const &) = delete;
+
+ // Leave the non-volatile default copy constructor unspecified (deleted by
+ // rule of 3)
+
+ // This template can serve as the copy constructor, but isn't chosen
+ // by =default in a class with a 'NonTCopy' member.
+ template <typename Self = NonTCopy>
+ NonTCopy(Self const &Other) : Val(Other.Val) {}
+
+ NonTCopy &operator=(NonTCopy const &) = default;
+
+ int Val{0};
+};
+
+#if defined(_MSC_VER) && _MSC_VER >= 1927 && !defined(__clang__)
+// Currently only true on recent MSVC releases.
+static_assert(std::is_trivially_copyable<NonTCopy>::value,
+ "Expect NonTCopy to be trivially copyable");
+
+static_assert(!std::is_trivially_copy_constructible<NonTCopy>::value,
+ "Expect NonTCopy not to be trivially copy constructible.");
+#endif // defined(_MSC_VER) && _MSC_VER >= 1927
+
+TEST(OptionalTest, DeletedCopyConstructor) {
+
+ // Expect compile to fail if 'trivial' version of
+ // optional_detail::OptionalStorage is chosen.
+ using NonTCopyOptT = Optional<NonTCopy>;
+ NonTCopyOptT NonTCopy1;
+
+ // Check that the Optional can be copy constructed.
+ NonTCopyOptT NonTCopy2{NonTCopy1};
+
+ // Check that the Optional can be copy assigned.
+ NonTCopy1 = NonTCopy2;
+}
+
+// Craft a class which is_trivially_copyable, but not
+// is_trivially_copy_assignable.
+class NonTAssign {
+public:
+ NonTAssign() = default;
+ NonTAssign(NonTAssign const &) = default;
+
+ // Delete the volatile copy assignment to engage the "rule of 3" and delete
+ // any unspecified copy assignment or constructor.
+ NonTAssign &operator=(volatile NonTAssign const &) = delete;
+
+ // Leave the non-volatile default copy assignment unspecified (deleted by rule
+ // of 3).
+
+ // This template can serve as the copy assignment, but isn't chosen
+ // by =default in a class with a 'NonTAssign' member.
+ template <typename Self = NonTAssign>
+ NonTAssign &operator=(Self const &Other) {
+ A = Other.A;
+ return *this;
+ }
+
+ int A{0};
+};
+
+#if defined(_MSC_VER) && _MSC_VER >= 1927 && !defined(__clang__)
+// Currently only true on recent MSVC releases.
+static_assert(std::is_trivially_copyable<NonTAssign>::value,
+ "Expect NonTAssign to be trivially copyable");
+
+static_assert(!std::is_trivially_copy_assignable<NonTAssign>::value,
+ "Expect NonTAssign not to be trivially assignable.");
+#endif // defined(_MSC_VER) && _MSC_VER >= 1927
+
+TEST(OptionalTest, DeletedCopyAssignment) {
+
+ // Expect compile to fail if 'trivial' version of
+ // optional_detail::OptionalStorage is chosen.
+ using NonTAssignOptT = Optional<NonTAssign>;
+ NonTAssignOptT NonTAssign1;
+
+ // Check that the Optional can be copy constructed.
+ NonTAssignOptT NonTAssign2{NonTAssign1};
+
+ // Check that the Optional can be copy assigned.
+ NonTAssign1 = NonTAssign2;
+}
+
+struct NoTMove {
+ NoTMove() = default;
+ NoTMove(NoTMove const &) = default;
+ NoTMove &operator=(NoTMove const &) = default;
+
+ // Delete move constructor / assignment. Compiler should fall-back to the
+ // trivial copy constructor / assignment in the trivial OptionalStorage
+ // specialization.
+ NoTMove(NoTMove &&) = delete;
+ NoTMove &operator=(NoTMove &&) = delete;
+
+ int Val{0};
+};
+
+TEST(OptionalTest, DeletedMoveConstructor) {
+ using NoTMoveOptT = Optional<NoTMove>;
+
+ NoTMoveOptT NonTMove1;
+ NoTMoveOptT NonTMove2{std::move(NonTMove1)};
+
+ NonTMove1 = std::move(NonTMove2);
+
+ static_assert(
+ std::is_trivially_copyable<NoTMoveOptT>::value,
+ "Expect Optional<NoTMove> to still use the trivial specialization "
+ "of OptionalStorage despite the deleted move constructor / assignment.");
+}
+
+class NoCopyStringMap {
+public:
+ NoCopyStringMap() = default;
+
+private:
+ llvm::StringMap<std::unique_ptr<int>> Map;
+};
+
+TEST(OptionalTest, DeletedCopyStringMap) {
+ // Old versions of gcc (7.3 and prior) instantiate the copy constructor when
+ // std::is_trivially_copyable is instantiated. This test will fail
+ // compilation if std::is_trivially_copyable is used in the OptionalStorage
+ // specialization condition by gcc <= 7.3.
+ Optional<NoCopyStringMap> TestInstantiation;
+}
+
#if LLVM_HAS_RVALUE_REFERENCE_THIS
TEST(OptionalTest, MoveGetValueOr) {