Conversation
6186a55 to
a072fa0
Compare
a072fa0 to
7520e2b
Compare
Replace with constexpr std::array-based StaticMap in util/static_map.h using C++20 features.
Add util::EnumToStr/EnumFromStr for kCamelCase <-> snake_case conversion to keep Python API unchanged.
Inline simplified version in util/atomic_bit_vector.h without iterator support (unused).
7520e2b to
4579220
Compare
| /** | ||
| * @brief Provides a constexpr immutable map-like lookup structure based on std::array. | ||
|
|
||
| * @tparam Key The key type. Must be comparable with `operator==`. | ||
| * @tparam Value The value type. | ||
| * @tparam N The number of key-value pairs in the map. | ||
| */ | ||
| template <typename Key, typename Value, std::size_t N> | ||
| struct StaticMap { | ||
| std::array<std::pair<Key, Value>, N> data; | ||
|
|
There was a problem hiding this comment.
Provides a constexpr immutable map-like lookup structure
But data is public field so anyone can modify it, like
util::StaticMap<int, int, 2> m{{{ {1, 10}, {2, 20} }}};
m.data[0].second = 123;So let's move data to a private section
| /** | ||
| * @brief Provides a constexpr immutable map-like lookup structure based on std::array. | ||
|
|
||
| * @tparam Key The key type. Must be comparable with `operator==`. |
There was a problem hiding this comment.
we can enforce it with concepts:
template <std::equality_comparable Key, typename Value, std::size_t N>
class StaticMap {| std::array<std::pair<Key, Value>, N> data; | ||
|
|
There was a problem hiding this comment.
we want our class to be map-like, but nobody stops us from providing duplicate keys:
constexpr util::StaticMap<int, int, 3> m{{{ {1, 10}, {1, 999}, {2, 20} }}};
// m.At(1) is 10This is bad because the code looks like 1 maps to 999, but it doesn’t. The first match wins because you linearly scan and return on first hit.
Of course in perfect world nobody will initialize StaticMap with such data, but someone could make a typo and enforcing no duplicates could save hours of debugging.
I suggest to add a compile-time duplicate check and static_assert if duplicates exist, smth like this:
template <std::equality_comparable Key, typename Value, std::size_t N>
class StaticMap {
public:
using Pair = std::pair<Key, Value>;
using Storage = std::array<Pair, N>;
constexpr explicit StaticMap(Storage data) : data_(std::move(data)) {
static_assert(!HasDuplicateKeys(data_), "StaticMap contains duplicate keys");
}
//...
private:
Storage data_;
static consteval bool HasDuplicateKeys(Storage const& data) {
for (std::size_t i = 0; i < N; ++i) {
for (std::size_t j = i + 1; j < N; ++j) {
if (data[i].first == data[j].first) return true;
}
}
return false;
}There was a problem hiding this comment.
note: consteval there forces that check to happen at compile time so things like this:
std::array<std::pair<int,int>, 2> runtime_data = ...;
util::StaticMap<int,int,2> maybe_good(runtime_data);won't compile at all. But I guess it makes sense here since StaticMap is constexpr, a replacement for frozen::unordered_map so basically if it’s not known at compile time, it shouldn't exist.
| [[nodiscard]] constexpr auto begin() const { | ||
| return data.begin(); | ||
| } | ||
|
|
||
| [[nodiscard]] constexpr auto end() const { | ||
| return data.end(); | ||
| } | ||
|
|
||
| [[nodiscard]] constexpr auto Size() const { | ||
| return N; | ||
| } | ||
|
|
||
| [[nodiscard]] constexpr bool Empty() const { | ||
| return N == 0; | ||
| } |
There was a problem hiding this comment.
hm, why is size and empty are not starting with lowercase letter? We have that rule it in our clang-format so that we could use things like range-based for without extra hassle?
| } | ||
|
|
||
| [[nodiscard]] constexpr Value const* Find(Key const& key) const { | ||
| for (auto const& pair : data) { |
There was a problem hiding this comment.
Use auto const& [k, v] : data_ to avoid doing pair.first and pair.second
| for (auto const& pair : data) { | ||
| if (pair.first == key) { | ||
| return pair.second; | ||
| } | ||
| } | ||
| throw std::out_of_range("StaticMap::at: key not found"); |
There was a problem hiding this comment.
We already have Find so we can just do this:
if (auto const* p = Find(key))
return *p;
throw std::out_of_range("StaticMap::At: key not found");| template <typename Key, typename Value, std::size_t N> | ||
| struct StaticMap { | ||
| std::array<std::pair<Key, Value>, N> data; |
There was a problem hiding this comment.
nit:
if you want, you can add some tests. Honestly this is quite simple class so they are probably not necessary, but small things like
TEST(StaticMapTest, FindAndAtWork) {
constexpr StaticMap<int, int, 3> map{{{{{1, 10}, {2, 20}, {3, 30}}}}};
auto const* p = map.Find(2);
ASSERT_NE(p, nullptr);
EXPECT_EQ(*p, 20);
EXPECT_EQ(map.At(3), 30);
}
TEST(StaticMapTest, MissingKey) {
constexpr StaticMap<int, int, 1> map{{{{{42, 100}}}}};
EXPECT_EQ(map.Find(0), nullptr);
EXPECT_THROW(map.At(0), std::out_of_range);
}won't hurt
| class AtomicBitVector { | ||
| public: | ||
| AtomicBitVector(size_t size) | ||
| : size_(size), | ||
| num_blocks_((size + kBitsPerBlock - 1) / kBitsPerBlock), | ||
| data_(std::make_unique<std::atomic<BlockType>[]>(num_blocks_)) {} | ||
|
|
||
| // Set bit to true. Returns previous value. | ||
| bool Set(size_t idx, std::memory_order order = std::memory_order_seq_cst) { | ||
| assert(idx < size_); | ||
| BlockType mask = kOne << BitOffset(idx); | ||
| return data_[BlockIndex(idx)].fetch_or(mask, order) & mask; | ||
| } | ||
|
|
||
| // Set bit to false. Returns previous value. | ||
| bool Reset(size_t idx, std::memory_order order = std::memory_order_seq_cst) { | ||
| assert(idx < size_); | ||
| BlockType mask = kOne << BitOffset(idx); | ||
| return data_[BlockIndex(idx)].fetch_and(~mask, order) & mask; | ||
| } | ||
|
|
||
| // Set bit to given value. Returns previous value. | ||
| bool Set(size_t idx, bool value, std::memory_order order = std::memory_order_seq_cst) { | ||
| return value ? Set(idx, order) : Reset(idx, order); | ||
| } | ||
|
|
||
| // Read bit value. | ||
| bool Test(size_t idx, std::memory_order order = std::memory_order_seq_cst) const { | ||
| assert(idx < size_); | ||
| BlockType mask = kOne << BitOffset(idx); | ||
| return data_[BlockIndex(idx)].load(order) & mask; | ||
| } | ||
|
|
||
| bool operator[](size_t idx) const { | ||
| return Test(idx); | ||
| } | ||
|
|
||
| constexpr size_t Size() const noexcept { | ||
| return size_; | ||
| } | ||
|
|
||
| private: | ||
| #if (ATOMIC_LLONG_LOCK_FREE == 2) | ||
| using BlockType = unsigned long long; | ||
| #elif (ATOMIC_LONG_LOCK_FREE == 2) | ||
| using BlockType = unsigned long; | ||
| #else | ||
| using BlockType = unsigned int; | ||
| #endif | ||
|
|
||
| static constexpr size_t kBitsPerBlock = std::numeric_limits<BlockType>::digits; | ||
| static constexpr BlockType kOne = 1; | ||
|
|
||
| static constexpr size_t BlockIndex(size_t bit) { | ||
| return bit / kBitsPerBlock; | ||
| } | ||
|
|
||
| static constexpr size_t BitOffset(size_t bit) { | ||
| return bit % kBitsPerBlock; | ||
| } | ||
|
|
||
| size_t size_; | ||
| size_t num_blocks_; | ||
| std::unique_ptr<std::atomic<BlockType>[]> data_; | ||
| }; |
There was a problem hiding this comment.
Well done!
Only one question: original code relied on value-initialization of std::atomic inside std::vector:
struct atomwrapper {
std::atomic<T> _a;
atomwrapper() : _a() { }
};
//...
std::vector<AtomicBlockType> data_;
//...
atomic_bv_t(size_t N) : _size(N),
kNumBlocks((N + kBitsPerBlock - 1) / kBitsPerBlock) {
data_.resize(kNumBlocks);
}That _a() is value-initialization, so initially we will have an array full of zeroes which is expected.
But here we have std::make_unique<std::atomic<BlockType>[]>(num_blocks_), which only default constructs std::atomic, so no value-initialization takes place
| /* | ||
| * Based on atomic_bitvector by Erik Garrison (Apache 2.0) | ||
| * https://github.com/ekg/atomicbitvector | ||
| * | ||
| * Simplified version without iterator support. | ||
| */ |
There was a problem hiding this comment.
I'm not an expert, but chatgpt tells me that in order to use apache 2.0 we need to:
- include copyright
- include license
- state changes
- include notice
It suggests this:
/*
* Based on atomic_bitvector by Erik Garrison
* https://github.com/ekg/atomicbitvector
*
* Copyright 2019 Erik Garrison
* Copyright 2019 Facebook, Inc. and its affiliates
*
* Modifications:
* - Removed iterator support
* - Replaced std::vector with std::unique_ptr<std::atomic[]>
* - Simplified API
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*/
Replace three unmaintained external libraries with internal solutions:
frozen->util::StaticMap(constexpr std::array)better_enums->magic_enum+util::EnumToStratomicbitvector->util::AtomicBitVectorThe enum naming conversion (
kCamelCase<->snake_case) is a workaround that may need rework. We need to agree on whether the current solution is acceptable, or whether we need to do something else, for example, native Python enums.