blob: 0ba9fa429fd30e0209e5fdf02a19e5287e30ceeb [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Helper class for a strongly-typed multiple variant token. Allows creating
// a token that can represent one of a collection of distinct token types.
// It would be great to replace this with a much simpler C++17 std::variant
// when that is available.
#ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_TOKENS_MULTI_TOKEN_H_
#define THIRD_PARTY_BLINK_PUBLIC_COMMON_TOKENS_MULTI_TOKEN_H_
#include <stdint.h>
#include <compare>
#include <limits>
#include <type_traits>
#include <utility>
#include <variant>
#include "base/types/variant_util.h"
#include "base/unguessable_token.h"
#include "third_party/blink/public/common/tokens/multi_token_internal.h"
namespace blink {
// `MultiToken<Tokens...>` is a variant of 2 or more token types. Each token
// type must be an instantiation of `base::TokenType`, and each token type must
// be unique within `Tokens...`. Unlike `base::UnguessableToken`, a `MultiToken`
// is always valid: there is no null state. Default constructing a `MultiToken`
// will create a `MultiToken` containing an instance of the first token type in
// `Tokens`.
//
// Usage:
//
// using CowToken = base::TokenType<class CowTokenTag>;
// using GoatToken = base::TokenType<class GoatTokenTag>;
// using UngulateToken = blink::MultiToken<CowToken, GoatToken>;
//
// void TeleportCow(const CowToken&);
// void TeleportGoat(const GoatToken&);
//
// void TeleportUngulate(const UngulateToken& token) {
// token.Visit(absl::Overload(
// [](const CowToken& cow_token) { TeleportCow(cow_token); },
// [](const GoatToken& goat_token) { TeleportGoat(goat_token); }));
// }
template <typename... Tokens>
requires(sizeof...(Tokens) > 1 &&
sizeof...(Tokens) <= std::numeric_limits<uint32_t>::max() &&
(internal::IsBaseToken<Tokens> && ...) &&
internal::AreAllUnique<Tokens...>)
class MultiToken {
public:
using Storage = std::variant<Tokens...>;
// In an ideal world, this would use StrongAlias, but a StrongAlias is not
// usable in a switch statement, even when the underlying type is integral.
enum class Tag : uint32_t {};
// A default constructed token will hold a default-constructed instance (i.e.
// randomly initialised) of the first token type in `Tokens...`.
MultiToken() = default;
template <typename T>
requires(internal::IsBaseToken<T> && internal::IsCompatible<T, Tokens...>)
// NOLINTNEXTLINE(google-explicit-constructor)
MultiToken(const T& token) : storage_(token) {}
MultiToken(const MultiToken&) = default;
// Construct from another compatible MultiToken.
template <typename... Ts>
requires(internal::IsCompatible<Ts, Tokens...> && ...)
explicit MultiToken(const MultiToken<Ts...>& multi_token)
: MultiToken(multi_token.Visit(
[](const auto& token) { return MultiToken(token); })) {}
template <typename T>
requires(internal::IsBaseToken<T> && internal::IsCompatible<T, Tokens...>)
MultiToken& operator=(const T& token) {
storage_ = token;
return *this;
}
MultiToken& operator=(const MultiToken&) = default;
// Assign from another compatible MultiToken.
template <typename... Ts>
requires(internal::IsCompatible<Ts, Tokens...> && ...)
MultiToken& operator=(const MultiToken<Ts...>& multi_token) {
return *this = multi_token.Visit(
[](const auto& token) { return MultiToken(token); });
}
~MultiToken() = default;
// Returns true iff `this` currently holds a token of type `T`.
template <typename T>
requires(internal::IsBaseToken<T> && internal::IsCompatible<T, Tokens...>)
bool Is() const {
return std::holds_alternative<T>(storage_);
}
// Returns `T` if `this` currently holds a token of type `T`; otherwise,
// crashes.
template <typename T>
requires(internal::IsBaseToken<T> && internal::IsCompatible<T, Tokens...>)
const T& GetAs() const {
return std::get<T>(storage_);
}
// Wrapper around std::visit() which invokes the provided functor on this
// MultiToken. The functor must return the same type when called with any of
// the MultiToken's alternatives.
template <typename Visitor>
decltype(auto) Visit(Visitor&& visitor) const {
return std::visit(std::forward<Visitor>(visitor), this->storage_);
}
// Comparison operators
constexpr friend auto operator<=>(const MultiToken& lhs,
const MultiToken& rhs) = default;
constexpr friend bool operator==(const MultiToken& lhs,
const MultiToken& rhs) = default;
template <typename T>
requires(internal::IsBaseToken<T> && internal::IsCompatible<T, Tokens...>)
friend auto operator<=>(const MultiToken& lhs, const T& rhs) {
return lhs <=> MultiToken(rhs);
}
template <typename T>
requires(internal::IsBaseToken<T> && internal::IsCompatible<T, Tokens...>)
friend bool operator==(const MultiToken& lhs, const T& rhs) {
return lhs == MultiToken(rhs);
}
// Hash functors for use in unordered containers.
struct Hasher {
using argument_type = MultiToken;
using result_type = size_t;
result_type operator()(const MultiToken& token) const {
return base::UnguessableTokenHash()(token.value());
}
};
template <typename H>
friend H AbslHashValue(H h, const MultiToken& token) {
return H::combine(std::move(h), token.value());
}
// Prefer the above helpers where possible. These methods are primarily useful
// for serialization/deserialization.
// Returns the underlying `base::UnguessableToken` of the currently held
// token.
const base::UnguessableToken& value() const {
return Visit([](const auto& token) -> const base::UnguessableToken& {
return token.value();
});
}
// 0-based index of the currently held token's type, based on its position in
// `Tokens...`.
Tag variant_index() const { return static_cast<Tag>(storage_.index()); }
// Returns the 0-based index that a token of type `T` would have if it were
// currently held.
template <typename T>
requires(internal::IsBaseToken<T> && internal::IsCompatible<T, Tokens...>)
static constexpr Tag IndexOf() {
return static_cast<Tag>(base::VariantIndexOfType<Storage, T>());
}
// Equivalent to `value().ToString()`.
std::string ToString() const {
return Visit([](const auto& token) { return token.ToString(); });
}
private:
Storage storage_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_PUBLIC_COMMON_TOKENS_MULTI_TOKEN_H_