Skip to content

Commit

Permalink
Make STL components not dependent on is_trivial(_v) (#5202)
Browse files Browse the repository at this point in the history
Co-authored-by: Stephan T. Lavavej <[email protected]>
  • Loading branch information
frederick-vs-ja and StephanTLavavej authored Jan 14, 2025
1 parent 6d5d462 commit a9861c9
Show file tree
Hide file tree
Showing 18 changed files with 75 additions and 56 deletions.
7 changes: 4 additions & 3 deletions stl/inc/__msvc_string_view.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1344,9 +1344,10 @@ class basic_string_view { // wrapper for any kind of contiguous character buffer
"Bad char_traits for basic_string_view; N4950 [string.view.template.general]/1 "
"\"The program is ill-formed if traits::char_type is not the same type as charT.\"");

static_assert(!is_array_v<_Elem> && is_trivial_v<_Elem> && is_standard_layout_v<_Elem>,
"The character type of basic_string_view must be a non-array trivial standard-layout type. See N4950 "
"[strings.general]/1.");
static_assert(!is_array_v<_Elem> && is_trivially_copyable_v<_Elem> && is_trivially_default_constructible_v<_Elem>
&& is_standard_layout_v<_Elem>,
"The character type of basic_string_view must be a non-array trivially copyable standard-layout type T where "
"is_trivially_default_constructible_v<T> is true. See N5001 [strings.general]/1.");

using traits_type = _Traits;
using value_type = _Elem;
Expand Down
3 changes: 2 additions & 1 deletion stl/inc/execution
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,8 @@ struct _Parallel_choose_max_chunk {

template <class _Ty>
struct alignas(_Ty) alignas(size_t) alignas(_Atomic_counter_t) _Circular_buffer { // work stealing deque extent type
static_assert(is_trivial_v<_Ty>, "Work stealing deques work only with trivial operations");
static_assert(is_trivially_copyable_v<_Ty> && is_trivially_default_constructible_v<_Ty>,
"Work stealing deques work only with trivial operations");

size_t _Log_size;
_Atomic_counter_t _Ref_count;
Expand Down
11 changes: 2 additions & 9 deletions stl/inc/filesystem
Original file line number Diff line number Diff line change
Expand Up @@ -352,19 +352,12 @@ namespace filesystem {

inline constexpr _Is_slash_oper _Is_slash{};

template <class _Ty>
_NODISCARD _Ty _Unaligned_load(const void* _Ptr) { // load a _Ty from _Ptr
static_assert(is_trivial_v<_Ty>, "Unaligned loads require trivial types");
_Ty _Tmp;
_CSTD memcpy(&_Tmp, _Ptr, sizeof(_Tmp));
return _Tmp;
}

_NODISCARD inline bool _Is_drive_prefix(const wchar_t* const _First) {
// test if _First points to a prefix of the form X:
// pre: _First points to at least 2 wchar_t instances
// pre: Little endian
auto _Value = _Unaligned_load<unsigned int>(_First);
unsigned int _Value;
_CSTD memcpy(&_Value, _First, sizeof(_Value)); // load from possibly unaligned address
_Value &= 0xFFFF'FFDFu; // transform lowercase drive letters into uppercase ones
_Value -= (static_cast<unsigned int>(L':') << (sizeof(wchar_t) * CHAR_BIT)) | L'A';
return _Value < 26;
Expand Down
6 changes: 4 additions & 2 deletions stl/inc/memory
Original file line number Diff line number Diff line change
Expand Up @@ -2246,7 +2246,8 @@ struct _NODISCARD _Reverse_destroy_multidimensional_n_guard {

template <class _Ty, size_t _Size>
void _Uninitialized_copy_multidimensional(const _Ty (&_In)[_Size], _Ty (&_Out)[_Size]) {
if constexpr (is_trivial_v<_Ty>) {
using _Item = remove_all_extents_t<_Ty>;
if constexpr (conjunction_v<is_trivially_copy_constructible<_Item>, is_trivially_destructible<_Item>>) {
_STD _Copy_memmove_n(_In, _Size, _Out);
} else if constexpr (is_array_v<_Ty>) {
_Reverse_destroy_multidimensional_n_guard<_Ty> _Guard{_Out, 0};
Expand Down Expand Up @@ -2610,7 +2611,8 @@ struct _NODISCARD _Reverse_destroy_multidimensional_n_al_guard {
template <class _Ty, size_t _Size, class _Alloc>
void _Uninitialized_copy_multidimensional_al(const _Ty (&_In)[_Size], _Ty (&_Out)[_Size], _Alloc& _Al) {
using _Item = remove_all_extents_t<_Ty>;
if constexpr (conjunction_v<is_trivial<_Ty>, _Uses_default_construct<_Alloc, _Item*, const _Item&>>) {
if constexpr (conjunction_v<is_trivially_copy_constructible<_Item>, is_trivially_destructible<_Item>,
_Uses_default_construct<_Alloc, _Item*, const _Item&>>) {
_STD _Copy_memmove_n(_In, _Size, _Out);
} else if constexpr (is_array_v<_Ty>) {
_Reverse_destroy_multidimensional_n_al_guard<_Ty, _Alloc> _Guard{_Out, 0, _Al};
Expand Down
6 changes: 3 additions & 3 deletions stl/inc/type_traits
Original file line number Diff line number Diff line change
Expand Up @@ -2299,7 +2299,7 @@ _NODISCARD inline size_t _Fnv1a_append_bytes(size_t _Val, const unsigned char* c
template <class _Ty>
_NODISCARD size_t _Fnv1a_append_range(const size_t _Val, const _Ty* const _First,
const _Ty* const _Last) noexcept { // accumulate range [_First, _Last) into partial FNV-1a hash _Val
static_assert(is_trivial_v<_Ty>, "Only trivial types can be directly hashed.");
static_assert(is_trivially_copyable_v<_Ty>, "Only trivially copyable types can be directly hashed.");
const auto _Firstb = reinterpret_cast<const unsigned char*>(_First);
const auto _Lastb = reinterpret_cast<const unsigned char*>(_Last);
return _Fnv1a_append_bytes(_Val, _Firstb, static_cast<size_t>(_Lastb - _Firstb));
Expand All @@ -2308,7 +2308,7 @@ _NODISCARD size_t _Fnv1a_append_range(const size_t _Val, const _Ty* const _First
template <class _Kty>
_NODISCARD size_t _Fnv1a_append_value(
const size_t _Val, const _Kty& _Keyval) noexcept { // accumulate _Keyval into partial FNV-1a hash _Val
static_assert(is_trivial_v<_Kty>, "Only trivial types can be directly hashed.");
static_assert(is_trivially_copyable_v<_Kty>, "Only trivially copyable types can be directly hashed.");
return _Fnv1a_append_bytes(_Val, &reinterpret_cast<const unsigned char&>(_Keyval), sizeof(_Kty));
}

Expand All @@ -2320,7 +2320,7 @@ _NODISCARD size_t _Hash_representation(const _Kty& _Keyval) noexcept { // bitwis
template <class _Kty>
_NODISCARD size_t _Hash_array_representation(
const _Kty* const _First, const size_t _Count) noexcept { // bitwise hashes the representation of an array
static_assert(is_trivial_v<_Kty>, "Only trivial types can be directly hashed.");
static_assert(is_trivially_copyable_v<_Kty>, "Only trivially copyable types can be directly hashed.");
return _Fnv1a_append_bytes(
_FNV_offset_basis, reinterpret_cast<const unsigned char*>(_First), _Count * sizeof(_Kty));
}
Expand Down
12 changes: 7 additions & 5 deletions stl/inc/xstring
Original file line number Diff line number Diff line change
Expand Up @@ -538,9 +538,10 @@ private:
"N4950 [string.require]/3 requires that the supplied "
"char_traits character type match the string's character type.");

static_assert(!is_array_v<_Elem> && is_trivial_v<_Elem> && is_standard_layout_v<_Elem>,
"The character type of basic_string must be a non-array trivial standard-layout type. See N4950 "
"[strings.general]/1.");
static_assert(!is_array_v<_Elem> && is_trivially_copyable_v<_Elem> && is_trivially_default_constructible_v<_Elem>
&& is_standard_layout_v<_Elem>,
"The character type of basic_string must be a non-array trivially copyable standard-layout type T where "
"is_trivially_default_constructible_v<T> is true. See N5001 [strings.general]/1.");

public:
using traits_type = _Traits;
Expand Down Expand Up @@ -573,12 +574,13 @@ private:
// _String_val::_Bx::_Ptr (type is pointer)
// _String_val::_Mysize (type is size_type)
// _String_val::_Myres (type is size_type)
// N4950 [strings.general]/1 says _Elem must be trivial standard-layout, so memcpy is safe.
// N5001 [strings.general]/1 says _Elem must be trivially copyable standard-layout, so memcpy is safe.
// We need to ask if pointer is safe to memcpy.
// size_type must be an unsigned integral type so memcpy is safe.
// We also need to disable memcpy if the user has supplied _Traits, since
// they can observe traits::assign and similar.
static constexpr bool _Can_memcpy_val = _Is_specialization_v<_Traits, char_traits> && is_trivial_v<pointer>;
static constexpr bool _Can_memcpy_val =
_Is_specialization_v<_Traits, char_traits> && is_trivially_copyable_v<pointer>;
// This offset skips over the _Container_base members, if any
static constexpr size_t _Memcpy_val_offset = _Size_after_ebco_v<_Container_base>;
static constexpr size_t _Memcpy_val_size = sizeof(_Scary_val) - _Memcpy_val_offset;
Expand Down
7 changes: 4 additions & 3 deletions tests/std/tests/GH_000431_copy_move_family/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ struct TrivialStruct {
return i == right.i;
}
};
STATIC_ASSERT(is_trivial_v<TrivialStruct>);
STATIC_ASSERT(is_trivially_copyable_v<TrivialStruct>);
STATIC_ASSERT(is_trivially_default_constructible_v<TrivialStruct>);

struct TriviallyCopyableStruct {
int i;
Expand All @@ -49,7 +50,7 @@ struct TriviallyCopyableStruct {
}
};
STATIC_ASSERT(is_trivially_copyable_v<TriviallyCopyableStruct>);
STATIC_ASSERT(!is_trivial_v<TriviallyCopyableStruct>);
STATIC_ASSERT(!is_trivially_default_constructible_v<TriviallyCopyableStruct>);

struct TriviallyMovableStruct {
int i;
Expand All @@ -65,7 +66,7 @@ struct TriviallyMovableStruct {
}
};
STATIC_ASSERT(is_trivially_copyable_v<TriviallyMovableStruct>);
STATIC_ASSERT(!is_trivial_v<TriviallyMovableStruct>);
STATIC_ASSERT(!is_trivially_default_constructible_v<TriviallyMovableStruct>);

enum int_enum : int {};
enum char_enum : char {};
Expand Down
19 changes: 10 additions & 9 deletions tests/std/tests/GH_000431_iter_copy_move_cat/test.compile.pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,14 +170,15 @@ void test_iter_cat_for_containers() {
struct TrivialStruct {
int i;
};
STATIC_ASSERT(is_trivial_v<TrivialStruct>);
STATIC_ASSERT(is_trivially_copyable_v<TrivialStruct>);
STATIC_ASSERT(is_trivially_default_constructible_v<TrivialStruct>);

struct TriviallyCopyableStruct {
int i;
TriviallyCopyableStruct();
};
STATIC_ASSERT(is_trivially_copyable_v<TriviallyCopyableStruct>);
STATIC_ASSERT(!is_trivial_v<TriviallyCopyableStruct>);
STATIC_ASSERT(!is_trivially_default_constructible_v<TriviallyCopyableStruct>);

struct TriviallyMovableStruct {
int i;
Expand All @@ -188,7 +189,7 @@ struct TriviallyMovableStruct {
TriviallyMovableStruct& operator=(TriviallyMovableStruct&&) = default;
};
STATIC_ASSERT(is_trivially_copyable_v<TriviallyMovableStruct>);
STATIC_ASSERT(!is_trivial_v<TriviallyMovableStruct>);
STATIC_ASSERT(!is_trivially_default_constructible_v<TriviallyMovableStruct>);

struct TriviallyConstructibleStruct {
int i;
Expand All @@ -204,7 +205,7 @@ STATIC_ASSERT(is_trivially_move_constructible_v<TriviallyConstructibleStruct>);
STATIC_ASSERT(!is_trivially_copy_assignable_v<TriviallyConstructibleStruct>);
STATIC_ASSERT(!is_trivially_move_assignable_v<TriviallyConstructibleStruct>);
STATIC_ASSERT(!is_trivially_copyable_v<TriviallyConstructibleStruct>);
STATIC_ASSERT(!is_trivial_v<TriviallyConstructibleStruct>);
STATIC_ASSERT(!is_trivially_default_constructible_v<TriviallyConstructibleStruct>);

struct TriviallyAssignableStruct {
int i;
Expand All @@ -220,7 +221,7 @@ STATIC_ASSERT(!is_trivially_move_constructible_v<TriviallyAssignableStruct>);
STATIC_ASSERT(is_trivially_copy_assignable_v<TriviallyAssignableStruct>);
STATIC_ASSERT(is_trivially_move_assignable_v<TriviallyAssignableStruct>);
STATIC_ASSERT(!is_trivially_copyable_v<TriviallyAssignableStruct>);
STATIC_ASSERT(!is_trivial_v<TriviallyAssignableStruct>);
STATIC_ASSERT(!is_trivially_default_constructible_v<TriviallyAssignableStruct>);

struct TriviallyCopyConstructibleStruct {
int i;
Expand All @@ -236,7 +237,7 @@ STATIC_ASSERT(!is_trivially_move_constructible_v<TriviallyCopyConstructibleStruc
STATIC_ASSERT(!is_trivially_copy_assignable_v<TriviallyCopyConstructibleStruct>);
STATIC_ASSERT(!is_trivially_move_assignable_v<TriviallyCopyConstructibleStruct>);
STATIC_ASSERT(!is_trivially_copyable_v<TriviallyCopyConstructibleStruct>);
STATIC_ASSERT(!is_trivial_v<TriviallyCopyConstructibleStruct>);
STATIC_ASSERT(!is_trivially_default_constructible_v<TriviallyCopyConstructibleStruct>);

struct TriviallyCopyAssignableStruct {
int i;
Expand All @@ -253,7 +254,7 @@ STATIC_ASSERT(!is_trivially_move_constructible_v<TriviallyCopyAssignableStruct>)
STATIC_ASSERT(is_trivially_copy_assignable_v<TriviallyCopyAssignableStruct>);
STATIC_ASSERT(!is_trivially_move_assignable_v<TriviallyCopyAssignableStruct>);
STATIC_ASSERT(!is_trivially_copyable_v<TriviallyCopyAssignableStruct>);
STATIC_ASSERT(!is_trivial_v<TriviallyCopyAssignableStruct>);
STATIC_ASSERT(!is_trivially_default_constructible_v<TriviallyCopyAssignableStruct>);

struct TriviallyMoveConstructibleStruct {
int i;
Expand All @@ -269,7 +270,7 @@ STATIC_ASSERT(is_trivially_move_constructible_v<TriviallyMoveConstructibleStruct
STATIC_ASSERT(!is_trivially_copy_assignable_v<TriviallyMoveConstructibleStruct>);
STATIC_ASSERT(!is_trivially_move_assignable_v<TriviallyMoveConstructibleStruct>);
STATIC_ASSERT(!is_trivially_copyable_v<TriviallyMoveConstructibleStruct>);
STATIC_ASSERT(!is_trivial_v<TriviallyMoveConstructibleStruct>);
STATIC_ASSERT(!is_trivially_default_constructible_v<TriviallyMoveConstructibleStruct>);

struct TriviallyMoveAssignableStruct {
int i;
Expand All @@ -285,7 +286,7 @@ STATIC_ASSERT(!is_trivially_move_constructible_v<TriviallyMoveAssignableStruct>)
STATIC_ASSERT(!is_trivially_copy_assignable_v<TriviallyMoveAssignableStruct>);
STATIC_ASSERT(is_trivially_move_assignable_v<TriviallyMoveAssignableStruct>);
STATIC_ASSERT(!is_trivially_copyable_v<TriviallyMoveAssignableStruct>);
STATIC_ASSERT(!is_trivial_v<TriviallyMoveAssignableStruct>);
STATIC_ASSERT(!is_trivially_default_constructible_v<TriviallyMoveAssignableStruct>);

struct EmptyBase {};

Expand Down
5 changes: 3 additions & 2 deletions tests/std/tests/P0009R18_mdspan_layout_left/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ constexpr void check_members(const extents<IndexType, Extents...>& ext, index_se
using Ext = extents<IndexType, Extents...>;
using Mapping = layout_left::mapping<Ext>;

// layout_left meets the layout mapping policy requirements and is a trivial type
// layout_left meets the requirements of N5001 [mdspan.layout.policy.overview]/1
static_assert(check_layout_mapping_policy_requirements<layout_left, Ext>());
static_assert(is_trivial_v<layout_left>);
static_assert(is_trivially_copyable_v<layout_left>);
static_assert(is_trivially_default_constructible_v<layout_left>);

// layout_left::mapping<Ext> is a trivially copyable type that models regular for each Ext
static_assert(is_trivially_copyable_v<Mapping>);
Expand Down
5 changes: 3 additions & 2 deletions tests/std/tests/P0009R18_mdspan_layout_right/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ constexpr void check_members(const extents<IndexType, Extents...>& ext, index_se
using Ext = extents<IndexType, Extents...>;
using Mapping = layout_right::mapping<Ext>;

// layout_right meets the layout mapping policy requirements and is a trivial type
// layout_right meets the requirements of N5001 [mdspan.layout.policy.overview]/1
static_assert(check_layout_mapping_policy_requirements<layout_right, Ext>());
static_assert(is_trivial_v<layout_right>);
static_assert(is_trivially_copyable_v<layout_right>);
static_assert(is_trivially_default_constructible_v<layout_right>);

// layout_right::mapping<Ext> is a trivially copyable type that models regular for each Ext
static_assert(is_trivially_copyable_v<Mapping>);
Expand Down
5 changes: 3 additions & 2 deletions tests/std/tests/P0009R18_mdspan_layout_stride/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,10 @@ constexpr void do_check_members(const extents<IndexType, Extents...>& ext,
using Strides = array<IndexType, sizeof...(Extents)>;
using Mapping = layout_stride::mapping<Ext>;

// layout_stride meets the layout mapping policy requirements and is a trivial type
// layout_stride meets the requirements of N5001 [mdspan.layout.policy.overview]/1
static_assert(check_layout_mapping_policy_requirements<layout_stride, Ext>());
static_assert(is_trivial_v<layout_stride>);
static_assert(is_trivially_copyable_v<layout_stride>);
static_assert(is_trivially_default_constructible_v<layout_stride>);

// layout_stride::mapping<Ext> is a trivially copyable type that models regular for each Ext
static_assert(is_trivially_copyable_v<Mapping>);
Expand Down
3 changes: 2 additions & 1 deletion tests/std/tests/P0009R18_mdspan_mdspan/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,8 @@ struct TrivialAccessor {
};

static_assert(check_accessor_policy_requirements<TrivialAccessor<int>>());
static_assert(is_trivial_v<TrivialAccessor<int>>);
static_assert(is_trivially_copyable_v<TrivialAccessor<int>>);
static_assert(is_trivially_default_constructible_v<TrivialAccessor<int>>);

template <class Ext, class Layout, template <class> class AccessorTemplate>
constexpr void check_modeled_concepts_and_member_types() {
Expand Down
3 changes: 2 additions & 1 deletion tests/std/tests/P0323R12_expected/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,8 @@ namespace test_unexpected {
namespace test_unexpect {
auto copy = unexpect;
static_assert(is_same_v<decltype(copy), unexpect_t>);
static_assert(is_trivial_v<unexpect_t>);
static_assert(is_trivially_copyable_v<unexpect_t>);
static_assert(is_trivially_default_constructible_v<unexpect_t>);
static_assert(is_empty_v<unexpect_t>);
} // namespace test_unexpect

Expand Down
2 changes: 1 addition & 1 deletion tests/std/tests/P0513R0_poisoning_the_hash/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ constexpr bool standard_hash_enabled() {
&& is_trivially_move_constructible_v<hash<T>> //
&& is_trivially_copy_assignable_v<hash<T>> //
&& is_trivially_move_assignable_v<hash<T>> //
&& is_trivial_v<hash<T>> // as a consequence of the above
&& is_trivially_copyable_v<hash<T>> //
&& is_same_v<typename hash<T>::argument_type, T> //
&& is_same_v<typename hash<T>::result_type, size_t> //
&& (noexcept(hash<T>{}(declval<const T&>())) == NoExcept) //
Expand Down
3 changes: 2 additions & 1 deletion tests/std/tests/P0896R4_P1614R2_comparisons/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ constexpr bool is_trivially_constexpr() {

// Not required, but likely portable nonetheless:
STATIC_ASSERT(std::is_empty_v<T>);
STATIC_ASSERT(std::is_trivial_v<T>);
STATIC_ASSERT(std::is_trivially_copyable_v<T>);
STATIC_ASSERT(std::is_trivially_default_constructible_v<T>);
STATIC_ASSERT(std::is_trivially_copy_constructible_v<T>);
STATIC_ASSERT(std::is_trivially_move_constructible_v<T>);
STATIC_ASSERT(std::is_trivially_copy_assignable_v<T>);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,8 @@ namespace dangling_test {
namespace result_test {
using ranges::in_found_result, ranges::in_fun_result, ranges::in_in_result, ranges::in_out_result,
ranges::in_in_out_result, ranges::in_out_out_result, ranges::min_max_result;
using std::is_aggregate_v, std::is_convertible_v, std::is_trivial_v;
using std::is_aggregate_v, std::is_convertible_v, std::is_trivially_copyable_v,
std::is_trivially_default_constructible_v;

// Validate the result types are:
// * aggregates
Expand All @@ -559,14 +560,23 @@ namespace result_test {
static_assert(is_aggregate_v<in_out_out_result<int, int, int>>);
static_assert(is_aggregate_v<min_max_result<int>>);

// * trivial when parameter types are trivial
static_assert(is_trivial_v<in_found_result<int>>);
static_assert(is_trivial_v<in_fun_result<int, int>>);
static_assert(is_trivial_v<in_in_result<int, int>>);
static_assert(is_trivial_v<in_out_result<int, int>>);
static_assert(is_trivial_v<in_in_out_result<int, int, int>>);
static_assert(is_trivial_v<in_out_out_result<int, int, int>>);
static_assert(is_trivial_v<min_max_result<int>>);
// * trivially copyable when parameter types are trivially copyable
static_assert(is_trivially_copyable_v<in_found_result<int>>);
static_assert(is_trivially_copyable_v<in_fun_result<int, int>>);
static_assert(is_trivially_copyable_v<in_in_result<int, int>>);
static_assert(is_trivially_copyable_v<in_out_result<int, int>>);
static_assert(is_trivially_copyable_v<in_in_out_result<int, int, int>>);
static_assert(is_trivially_copyable_v<in_out_out_result<int, int, int>>);
static_assert(is_trivially_copyable_v<min_max_result<int>>);

// * trivially default constructible when parameter types are trivially default constructible
static_assert(is_trivially_default_constructible_v<in_found_result<int>>);
static_assert(is_trivially_default_constructible_v<in_fun_result<int, int>>);
static_assert(is_trivially_default_constructible_v<in_in_result<int, int>>);
static_assert(is_trivially_default_constructible_v<in_out_result<int, int>>);
static_assert(is_trivially_default_constructible_v<in_in_out_result<int, int, int>>);
static_assert(is_trivially_default_constructible_v<in_out_out_result<int, int, int>>);
static_assert(is_trivially_default_constructible_v<min_max_result<int>>);

// * usable with structured bindings
constexpr bool test_bindings_in_found_result() {
Expand Down
Loading

0 comments on commit a9861c9

Please sign in to comment.