-
Notifications
You must be signed in to change notification settings - Fork 92
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#0: Add StrongTypeHandle to help creating non-clashing alias types (#…
…16309) ### Ticket N/A ### Problem description In tt-metal, primitive types such as `uint32_t` / `int` often get used to alias distinct meaningful concepts like `DeviceId`, `chip_id`, `ethernet_channel_t`, etc. This is error prone, as this allows interchangeable uses with their underlying types. ### What's changed Add `StrongType` as a way to create strong aliases that are incompatible with each other: * Integrated with `std::hash` and `std::ostream` (if the underlying type supports these). * Tests / documentation. ### Checklist - [X] [Build post commit](https://github.com/tenstorrent/tt-metal/actions/runs/12486159245) - [X] New/Existing tests provide coverage for changes
- Loading branch information
1 parent
2a86ff7
commit 7949c86
Showing
3 changed files
with
151 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
#include <gtest/gtest.h> | ||
#include <gmock/gmock.h> | ||
|
||
#include <unordered_set> | ||
|
||
#include "tt_metal/tt_stl/strong_type.hpp" | ||
|
||
using MyIntId = tt::stl::StrongType<int, struct MyIntIdTag>; | ||
using MyStringId = tt::stl::StrongType<std::string, struct MyStringIdTag>; | ||
|
||
namespace tt::stl { | ||
namespace { | ||
|
||
using ::testing::ElementsAre; | ||
using ::testing::IsNull; | ||
using ::testing::UnorderedElementsAre; | ||
|
||
TEST(StrongTypeTest, Basic) { | ||
MyIntId my_int_id1(42); | ||
MyIntId my_int_id2(43); | ||
|
||
EXPECT_EQ(*my_int_id1, 42); | ||
EXPECT_LT(*my_int_id1, *my_int_id2); | ||
|
||
my_int_id1 = MyIntId(43); | ||
EXPECT_EQ(my_int_id1, my_int_id2); | ||
} | ||
|
||
TEST(StrongTypeTest, UseInContainers) { | ||
std::unordered_set<MyIntId> unordered; | ||
std::set<MyIntId> ordered; | ||
|
||
unordered.insert(MyIntId(42)); | ||
unordered.insert(MyIntId(43)); | ||
|
||
ordered.insert(MyIntId(1)); | ||
ordered.insert(MyIntId(2)); | ||
ordered.insert(MyIntId(3)); | ||
|
||
EXPECT_THAT(unordered, UnorderedElementsAre(MyIntId(42), MyIntId(43))); | ||
EXPECT_THAT(ordered, ElementsAre(MyIntId(1), MyIntId(2), MyIntId(3))); | ||
} | ||
|
||
TEST(StrongTypeTest, StreamingOperator) { | ||
std::stringstream ss; | ||
ss << MyStringId("hello world"); | ||
EXPECT_EQ(ss.str(), "hello world"); | ||
} | ||
|
||
TEST(StrongTypeTest, MoveOnlyType) { | ||
using MoveOnlyType = StrongType<std::unique_ptr<int>, struct MoveOnlyTag>; | ||
|
||
MoveOnlyType from(std::make_unique<int>(42)); | ||
EXPECT_EQ(**from, 42); | ||
|
||
MoveOnlyType to = std::move(from); | ||
|
||
// NOLINTNEXTLINE(bugprone-use-after-move) | ||
EXPECT_THAT(*from, IsNull()); | ||
EXPECT_EQ(**to, 42); | ||
} | ||
|
||
} // namespace | ||
} // namespace tt::stl |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
// SPDX-FileCopyrightText: © 2024 Tenstorrent Inc. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
#pragma once | ||
|
||
#include <utility> | ||
|
||
namespace tt::stl { | ||
|
||
// `StrongType` provides a strongly-typed wrapper around a value to prevent accidental type conversions. | ||
// | ||
// This is useful when creating aliases that rely on a primitive type; for example instead of using `uint32_t` as | ||
// `DeviceId` directly, wrap in `StrongType<uint32_t, struct DeviceIdTag>` to prevent accidental assignment | ||
// from `uint32_t`. Here, the 'tag' is used to disambiguate the type, and to create distinct wrappers relying on | ||
// `uint32_t`. | ||
// | ||
// | ||
// Example usage: | ||
// | ||
// // Create strong types. | ||
// // `struct`s for the tag can be supplied as shown, despite of being incomplete: | ||
// | ||
// using UserId = StrongType<uint32_t, struct UserIdTag>; | ||
// using GroupId = StrongType<uint32_t, struct GroupIdTag>; | ||
// using Username = StrongType<std::string, struct UsernameTag>; | ||
// | ||
// | ||
// // The different types cannot be assigned to each other: | ||
// UserId user_id(42); | ||
// GroupId group_id(45); | ||
// user_id = group_id; // does not compile! | ||
// | ||
// Username name("john_doe"); | ||
// name = "jane_doe"; // does not compile! | ||
// name = Username("jane_doe"); // instantiate explicitly. | ||
// | ||
// // Access the underlying value: | ||
// uint32_t raw_user_id = *user_id; | ||
// assert(*user_id < *group_id); | ||
// | ||
// // Strong types work with standard containers and the streaming operator, as long as the underlying type is | ||
// // hashable and comparable. | ||
// | ||
// std::unordered_set<UserId> user_set; | ||
// user_set.insert(UserId(1)); | ||
// user_set.insert(UserId(2)); | ||
|
||
// std::map<UserId, Username> user_map; | ||
// user_map.emplace(UserId(1), Username("John Doe")); | ||
// | ||
// std::cout << user_map.at(UserId(1)) << std::endl; // "John Doe" | ||
// | ||
template <typename T, typename Tag> | ||
class StrongType { | ||
public: | ||
explicit StrongType(T v) : value_(std::move(v)) {} | ||
|
||
StrongType(const StrongType&) = default; | ||
StrongType(StrongType&&) = default; | ||
StrongType& operator=(const StrongType&) = default; | ||
StrongType& operator=(StrongType&&) = default; | ||
|
||
const T& operator*() const { return value_; } | ||
|
||
auto operator<=>(const StrongType&) const = default; | ||
|
||
private: | ||
T value_; | ||
}; | ||
|
||
} // namespace tt::stl | ||
|
||
template <typename T, typename Tag> | ||
std::ostream& operator<<(std::ostream& os, const tt::stl::StrongType<T, Tag>& h) { | ||
return os << *h; | ||
} | ||
|
||
template <typename T, typename Tag> | ||
struct std::hash<tt::stl::StrongType<T, Tag>> { | ||
std::size_t operator()(const tt::stl::StrongType<T, Tag>& h) const noexcept { return std::hash<T>{}(*h); } | ||
}; |