FTL: Refine container semantics
Allow constructing StaticVector and SmallVector by moving smaller
convertible vectors, which so far incurred a copy. Allow the same
copy/move conversion for SmallMap.
Consistently with StaticVector, do not require assignable elements for
the SmallVector to be assignable, which notably enables SmallMap to be
assignable despite its const keys.
Allow comparison of convertible containers.
Bug: 185536303
Test: ftl_test
Change-Id: I35923e794ef26178dc3072f514dea7ad5600bc15
diff --git a/libs/ftl/small_map_test.cpp b/libs/ftl/small_map_test.cpp
index ee650e5..1740a2b 100644
--- a/libs/ftl/small_map_test.cpp
+++ b/libs/ftl/small_map_test.cpp
@@ -96,6 +96,33 @@
}
}
+TEST(SmallMap, Assign) {
+ {
+ // Same types; smaller capacity.
+ SmallMap map1 = ftl::init::map<char, std::string>('k', "kilo")('M', "mega")('G', "giga");
+ const SmallMap map2 = ftl::init::map('T', "tera"s)('P', "peta"s);
+
+ map1 = map2;
+ EXPECT_EQ(map1, map2);
+ }
+ {
+ // Convertible types; same capacity.
+ SmallMap map1 = ftl::init::map<char, std::string>('M', "mega")('G', "giga");
+ const SmallMap map2 = ftl::init::map('T', "tera")('P', "peta");
+
+ map1 = map2;
+ EXPECT_EQ(map1, map2);
+ }
+ {
+ // Convertible types; zero capacity.
+ SmallMap<char, std::string, 0> map1 = ftl::init::map('M', "mega")('G', "giga");
+ const SmallMap<char, std::string, 0> map2 = ftl::init::map('T', "tera")('P', "peta");
+
+ map1 = map2;
+ EXPECT_EQ(map1, map2);
+ }
+}
+
TEST(SmallMap, UniqueKeys) {
{
// Duplicate mappings are discarded.
diff --git a/libs/ftl/small_vector_test.cpp b/libs/ftl/small_vector_test.cpp
index 4237496..b662a81 100644
--- a/libs/ftl/small_vector_test.cpp
+++ b/libs/ftl/small_vector_test.cpp
@@ -138,6 +138,116 @@
}
}
+TEST(SmallVector, Copy) {
+ {
+ // Same capacity.
+ const SmallVector vector = {"snow"s, "cone"s};
+
+ SmallVector<const std::string, 2> copy(vector);
+ EXPECT_EQ(copy, vector);
+
+ // The vector is assignable even if T is const.
+ const SmallVector<std::string, 2> other = {"tiramisu"s};
+ copy = other;
+ EXPECT_EQ(copy, other);
+ }
+ {
+ // From smaller capacity.
+ const SmallVector vector = {"snow"s, "cone"s};
+
+ SmallVector<const std::string, 3> copy(vector);
+ EXPECT_EQ(copy, vector);
+
+ // The vector is assignable even if T is const.
+ const SmallVector other = {"tiramisu"s};
+ copy = other;
+ EXPECT_EQ(copy, other);
+ }
+ {
+ // To zero capacity.
+ const SmallVector vector = {"snow"s, "cone"s};
+
+ SmallVector<const std::string, 0> copy(vector);
+ EXPECT_EQ(copy, vector);
+
+ // The vector is assignable even if T is const.
+ const SmallVector other = {"tiramisu"s};
+ copy = other;
+ EXPECT_EQ(copy, other);
+ }
+ {
+ // From/to zero capacity.
+ const SmallVector<std::string, 0> vector = {"snow"s, "cone"s};
+
+ SmallVector<const std::string, 0> copy(vector);
+ EXPECT_EQ(copy, vector);
+
+ // The vector is assignable even if T is const.
+ const SmallVector<std::string, 0> other = {"tiramisu"s};
+ copy = other;
+ EXPECT_EQ(copy, other);
+ }
+}
+
+TEST(SmallVector, Move) {
+ {
+ // Same capacity.
+ SmallVector vector = {"snow"s, "cone"s};
+
+ SmallVector<const std::string, 2> move(std::move(vector));
+ EXPECT_TRUE(vector.empty());
+ EXPECT_EQ(move, (SmallVector{"snow"s, "cone"s}));
+
+ // The vector is assignable even if T is const.
+ SmallVector<std::string, 2> other = {"tiramisu"s};
+ move = std::move(other);
+ EXPECT_TRUE(other.empty());
+ EXPECT_EQ(move, (SmallVector{"tiramisu"s}));
+ }
+ {
+ // From smaller capacity.
+ SmallVector vector = {"snow"s, "cone"s};
+
+ SmallVector<const std::string, 3> move(std::move(vector));
+ EXPECT_TRUE(vector.empty());
+ EXPECT_EQ(move, (SmallVector{"snow"s, "cone"s}));
+
+ // The vector is assignable even if T is const.
+ SmallVector other = {"tiramisu"s};
+ move = std::move(other);
+ EXPECT_TRUE(other.empty());
+ EXPECT_EQ(move, (SmallVector{"tiramisu"s}));
+ }
+ {
+ // To zero capacity.
+ SmallVector vector = {"snow"s, "cone"s};
+
+ SmallVector<const std::string, 0> move(std::move(vector));
+ EXPECT_TRUE(vector.empty());
+ EXPECT_EQ(move, (SmallVector{"snow"s, "cone"s}));
+
+ // The vector is assignable even if T is const.
+ SmallVector other = {"tiramisu"s};
+ move = std::move(other);
+ EXPECT_TRUE(other.empty());
+ EXPECT_EQ(move, (SmallVector{"tiramisu"s}));
+ }
+ {
+ // From/to zero capacity.
+ SmallVector<std::string, 0> vector = {"snow"s, "cone"s};
+
+ SmallVector<const std::string, 0> move(std::move(vector));
+ EXPECT_TRUE(vector.empty());
+ EXPECT_EQ(move, (SmallVector{"snow"s, "cone"s}));
+
+ // The vector is assignable even if T is const.
+ SmallVector<std::string, 0> other = {"tiramisu"s};
+ move = std::move(other);
+ EXPECT_TRUE(other.empty());
+ EXPECT_EQ(move, (SmallVector{"tiramisu"s}));
+ }
+}
+
TEST(SmallVector, String) {
SmallVector<char, 10> chars;
char c = 'a';
@@ -366,21 +476,20 @@
bool alive = true;
};
-void swap(DestroyCounts& lhs, DestroyCounts& rhs) {
- std::swap(lhs.alive, rhs.alive);
-}
-
} // namespace
TEST(SmallVector, Destroy) {
int live = 0;
int dead = 0;
-
- { SmallVector<DestroyCounts, 3> counts; }
+ {
+ // Empty.
+ SmallVector<DestroyCounts, 3> counts;
+ }
EXPECT_EQ(0, live);
EXPECT_EQ(0, dead);
{
+ // Static.
SmallVector<DestroyCounts, 3> counts;
counts.emplace_back(live, dead);
counts.emplace_back(live, dead);
@@ -393,6 +502,7 @@
live = 0;
{
+ // Dynamic.
SmallVector<DestroyCounts, 3> counts;
counts.emplace_back(live, dead);
counts.emplace_back(live, dead);
@@ -406,12 +516,13 @@
live = dead = 0;
{
+ // Copy.
SmallVector<DestroyCounts, 2> counts;
counts.emplace_back(live, dead);
counts.emplace_back(live, dead);
counts.emplace_back(live, dead);
- auto copy = counts;
+ const auto copy = counts;
EXPECT_TRUE(copy.dynamic());
}
EXPECT_EQ(6, live);
@@ -419,12 +530,13 @@
live = dead = 0;
{
+ // Move.
SmallVector<DestroyCounts, 2> counts;
counts.emplace_back(live, dead);
counts.emplace_back(live, dead);
counts.emplace_back(live, dead);
- auto move = std::move(counts);
+ const auto move = std::move(counts);
EXPECT_TRUE(move.dynamic());
}
EXPECT_EQ(3, live);
@@ -432,6 +544,7 @@
live = dead = 0;
{
+ // Swap.
SmallVector<DestroyCounts, 2> counts1;
counts1.emplace_back(live, dead);
counts1.emplace_back(live, dead);
@@ -448,7 +561,10 @@
swap(counts1, counts2);
+ EXPECT_EQ(1u, counts1.size());
EXPECT_FALSE(counts1.dynamic());
+
+ EXPECT_EQ(3u, counts2.size());
EXPECT_TRUE(counts2.dynamic());
EXPECT_EQ(0, live);
@@ -465,29 +581,82 @@
int dead = 0;
SmallVector<DestroyCounts, 2> counts;
- counts.emplace_back(live, dead);
- counts.emplace_back(live, dead);
+ {
+ // Static.
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
- counts.clear();
+ counts.clear();
- EXPECT_TRUE(counts.empty());
- EXPECT_FALSE(counts.dynamic());
-
+ EXPECT_TRUE(counts.empty());
+ EXPECT_FALSE(counts.dynamic());
+ }
EXPECT_EQ(2, live);
EXPECT_EQ(0, dead);
live = 0;
- counts.emplace_back(live, dead);
- counts.emplace_back(live, dead);
- counts.emplace_back(live, dead);
+ {
+ // Dynamic.
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
- counts.clear();
+ counts.clear();
- EXPECT_TRUE(counts.empty());
- EXPECT_TRUE(counts.dynamic());
-
+ EXPECT_TRUE(counts.empty());
+ EXPECT_TRUE(counts.dynamic());
+ }
EXPECT_EQ(3, live);
EXPECT_EQ(2, dead);
}
+TEST(SmallVector, Promote) {
+ {
+ const std::vector vector = {"snow"s, "cone"s};
+ EXPECT_EQ(vector, SmallVector("snow"s, "cone"s).promote());
+ EXPECT_EQ(vector, (SmallVector<std::string, 0>({"snow"s, "cone"s}).promote()));
+ }
+
+ int live = 0;
+ int dead = 0;
+
+ std::vector<DestroyCounts> vector;
+ {
+ // Static.
+ SmallVector<DestroyCounts, 3> counts;
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
+
+ vector = std::move(counts).promote();
+
+ ASSERT_EQ(2u, vector.size());
+ EXPECT_TRUE(vector[0].alive);
+ EXPECT_TRUE(vector[1].alive);
+ }
+ EXPECT_EQ(0, live);
+ EXPECT_EQ(2, dead);
+
+ vector.clear();
+ live = dead = 0;
+ {
+ // Dynamic.
+ SmallVector<DestroyCounts, 2> counts;
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
+
+ EXPECT_EQ(2, dead);
+ dead = 0;
+
+ vector = std::move(counts).promote();
+
+ ASSERT_EQ(3u, vector.size());
+ EXPECT_TRUE(vector[0].alive);
+ EXPECT_TRUE(vector[1].alive);
+ EXPECT_TRUE(vector[2].alive);
+ }
+ EXPECT_EQ(0, live);
+ EXPECT_EQ(0, dead);
+}
+
} // namespace android::test
diff --git a/libs/ftl/static_vector_test.cpp b/libs/ftl/static_vector_test.cpp
index 2de3ad2..0e10a5d 100644
--- a/libs/ftl/static_vector_test.cpp
+++ b/libs/ftl/static_vector_test.cpp
@@ -144,6 +144,64 @@
}
}
+TEST(StaticVector, Copy) {
+ {
+ // Same capacity.
+ const StaticVector vector = {"snow"s, "cone"s};
+
+ StaticVector<const std::string, 2> copy(vector);
+ EXPECT_EQ(copy, vector);
+
+ // The vector is assignable even if T is const.
+ const StaticVector<std::string, 2> other = {"tiramisu"s};
+ copy = other;
+ EXPECT_EQ(copy, other);
+ }
+ {
+ // From smaller capacity.
+ const StaticVector vector = {"snow"s, "cone"s};
+
+ StaticVector<const std::string, 3> copy(vector);
+ EXPECT_EQ(copy, vector);
+
+ // The vector is assignable even if T is const.
+ const StaticVector other = {"tiramisu"s};
+ copy = other;
+ EXPECT_EQ(copy, other);
+ }
+}
+
+TEST(StaticVector, Move) {
+ {
+ // Same capacity.
+ StaticVector vector = {"snow"s, "cone"s};
+
+ StaticVector<const std::string, 2> move(std::move(vector));
+ EXPECT_TRUE(vector.empty());
+ EXPECT_EQ(move, (StaticVector{"snow"s, "cone"s}));
+
+ // The vector is assignable even if T is const.
+ StaticVector<std::string, 2> other = {"tiramisu"s};
+ move = std::move(other);
+ EXPECT_TRUE(other.empty());
+ EXPECT_EQ(move, (StaticVector{"tiramisu"s}));
+ }
+ {
+ // From smaller capacity.
+ StaticVector vector = {"snow"s, "cone"s};
+
+ StaticVector<const std::string, 3> move(std::move(vector));
+ EXPECT_TRUE(vector.empty());
+ EXPECT_EQ(move, (StaticVector{"snow"s, "cone"s}));
+
+ // The vector is assignable even if T is const.
+ StaticVector other = {"tiramisu"s};
+ move = std::move(other);
+ EXPECT_TRUE(other.empty());
+ EXPECT_EQ(move, (StaticVector{"tiramisu"s}));
+ }
+}
+
TEST(StaticVector, String) {
StaticVector<char, 10> chars;
char c = 'a';
@@ -328,21 +386,20 @@
bool alive = true;
};
-void swap(DestroyCounts& lhs, DestroyCounts& rhs) {
- std::swap(lhs.alive, rhs.alive);
-}
-
} // namespace
TEST(StaticVector, Destroy) {
int live = 0;
int dead = 0;
-
- { StaticVector<DestroyCounts, 5> counts; }
+ {
+ // Empty.
+ StaticVector<DestroyCounts, 5> counts;
+ }
EXPECT_EQ(0, live);
EXPECT_EQ(0, dead);
{
+ // Non-empty.
StaticVector<DestroyCounts, 5> counts;
counts.emplace_back(live, dead);
counts.emplace_back(live, dead);
@@ -353,30 +410,33 @@
live = 0;
{
+ // Copy.
StaticVector<DestroyCounts, 5> counts;
counts.emplace_back(live, dead);
counts.emplace_back(live, dead);
counts.emplace_back(live, dead);
- auto copy = counts;
+ const auto copy = counts;
}
EXPECT_EQ(6, live);
EXPECT_EQ(0, dead);
live = 0;
{
+ // Move.
StaticVector<DestroyCounts, 5> counts;
counts.emplace_back(live, dead);
counts.emplace_back(live, dead);
counts.emplace_back(live, dead);
- auto move = std::move(counts);
+ const auto move = std::move(counts);
}
EXPECT_EQ(3, live);
EXPECT_EQ(3, dead);
live = dead = 0;
{
+ // Swap.
StaticVector<DestroyCounts, 5> counts1;
counts1.emplace_back(live, dead);
counts1.emplace_back(live, dead);
@@ -384,29 +444,34 @@
StaticVector<DestroyCounts, 5> counts2;
counts2.emplace_back(live, dead);
+ counts2.emplace_back(live, dead);
swap(counts1, counts2);
+ EXPECT_EQ(2u, counts1.size());
+ EXPECT_EQ(3u, counts2.size());
+
EXPECT_EQ(0, live);
- EXPECT_EQ(2, dead);
+ EXPECT_EQ(7, dead); // 3 moves per swap, plus 1 move.
dead = 0;
}
- EXPECT_EQ(4, live);
+ EXPECT_EQ(5, live);
EXPECT_EQ(0, dead);
}
TEST(StaticVector, Clear) {
int live = 0;
int dead = 0;
+ {
+ StaticVector<DestroyCounts, 5> counts;
+ counts.emplace_back(live, dead);
+ counts.emplace_back(live, dead);
- StaticVector<DestroyCounts, 5> counts;
- counts.emplace_back(live, dead);
- counts.emplace_back(live, dead);
+ counts.clear();
- counts.clear();
-
- EXPECT_TRUE(counts.empty());
+ EXPECT_TRUE(counts.empty());
+ }
EXPECT_EQ(2, live);
EXPECT_EQ(0, dead);
}