OpenSiv3D
OpenSiv3D copied to clipboard
Allocator をコンテナのアロケータとして使えるようにする
発生している問題
s3d::Array
や std::vector
などのコンテナのアロケータとして s3d::Allocator
を使用しようとすると、次のようにコンパイルエラーが出ます。
# include <Siv3D.hpp> // OpenSiv3D v0.6.5
void Main()
{
Array<int32, Allocator<int32, 16>> a; // error!
Grid<int32, Allocator<int32, 16>> g; // error!
std::vector<int32, Allocator<int32, 16>> v; // error!
std::list<int32, Allocator<int32, 16>> l; // error!
std::set<int32, std::less<int32>, Allocator<int32, 16>> s; // error!
}
ビルドを開始しました...
1>------ ビルド開始: プロジェクト: OpenSiv3D_0.6.5_Test, 構成: Debug x64 ------
1>Main.cpp
1>C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.33.31629\include\xmemory(445,66): error C2027: 認識できない型 'std::_Replace_first_parameter<_Other,_Ty>' が使われています。
1> with
1> [
1> _Other=s3d::int32,
1> _Ty=s3d::Allocator<s3d::int32,16>
1> ]
……
これは Visual Studio の出力結果ですが、 GCC でも同様のエラーが出ます。
エラー中の std::_Replace_first_parameter
の実装は次のようになっています。
template <class _Newfirst, class _Ty>
struct _Replace_first_parameter;
template <class _Newfirst, template <class, class...> class _Ty, class _First, class... _Rest>
struct _Replace_first_parameter<_Newfirst, _Ty<_First, _Rest...>> { // given _Ty<_First, _Rest...>, replace _First
using type = _Ty<_Newfirst, _Rest...>;
};
https://github.com/microsoft/STL/blob/main/stl/inc/xutility#L161-L167 より
ここで、 s3d::Allocator
が非型テンプレート引数を持たないことが要求されます。
しかし、 s3d::Allocator
は std::size_t
型の非型テンプレート引数を持ちます。
提案
s3d::Allocator
が非型テンプレート引数を持たないように変更するか、新たにアロケータを作って s3d::Allocator
を非推奨にするなどして、対応すべきだと思います。
下の折り畳み要素中に新たにアロケータを作る場合の実装案を示します。
実装案
# include <Siv3D.hpp> // OpenSiv3D v0.6.5
// ==========
// AlignmentAllocator.hpp
// ==========
namespace s3d
{
namespace detail
{
template <class Type, class Alignment>
struct AlignmentAllocatorImpl
{
static_assert(!std::is_const_v<Type>, "AlignmentAllocator<const Type> is ill-formed.");
using value_type = Type;
using propagate_on_container_move_assignment = std::true_type;
using is_always_equal = std::true_type;
static constexpr size_t alignment = Alignment::value;
SIV3D_NODISCARD_CXX20
constexpr AlignmentAllocatorImpl() noexcept = default;
SIV3D_NODISCARD_CXX20
constexpr AlignmentAllocatorImpl(const AlignmentAllocatorImpl&) noexcept = default;
template <class OtherType, class OtherAlignment>
SIV3D_NODISCARD_CXX20
constexpr AlignmentAllocatorImpl(const AlignmentAllocatorImpl<OtherType, OtherAlignment>&) noexcept {}
[[nodiscard]]
# if SIV3D_PLATFORM(WINDOWS)
__declspec(allocator)
# endif
Type* allocate(size_t n);
void deallocate(Type* p, size_t);
};
template <class T1, class A1, class T2, class A2>
[[nodiscard]]
inline constexpr bool operator ==(const AlignmentAllocatorImpl<T1, A1>&, const AlignmentAllocatorImpl<T2, A2>&) noexcept;
}
template <class Type, std::size_t Alignment = alignof(Type)>
using AlignmentAllocator = detail::AlignmentAllocatorImpl<Type, std::integral_constant<std::size_t, Alignment>>;
}
// ==========
// AlignmentAllocator.ipp
// ==========
namespace s3d::detail
{
template <class Type, class Alignment>
Type* AlignmentAllocatorImpl<Type, Alignment>::allocate(const size_t n)
{
return AlignedMalloc<Type, Alignment::value>(n);
}
template <class Type, class Alignment>
void AlignmentAllocatorImpl<Type, Alignment>::deallocate(Type* const p, const size_t)
{
AlignedFree(p);
}
template <class T1, class A1, class T2, class A2>
inline constexpr bool operator ==(const AlignmentAllocatorImpl<T1, A1>&, const AlignmentAllocatorImpl<T2, A2>&) noexcept
{
return true;
}
}
// ==========
// Main.cpp
// ==========
void Main()
{
//Array<int32, Allocator<int32, 16>> a{ 1, 2, 3 }; // error!
Array<int32, AlignmentAllocator<int32, 16>> a{ 1, 2, 3 }; // ok
Print << a;
while (System::Update());
}
非型テンプレート引数を持たないような実装以外に、 Cpp17Allocator の制約に基づいて適切な typename Allocator::template rebind<U>::other
を提供する方法もあると思われます。
ご指摘ありがとうございます。
その方法であれば、 s3d::Allocator
の一部を修正するだけでよいので、私の提案した方法より適切だと思います。
実装案
# include <Siv3D.hpp> // OpenSiv3D v0.6.5
namespace s3d::improved
{
// ==========
// Allocator.hpp
// ==========
/// @brief メモリアライメント対応アロケータ
/// @tparam Type アロケーションするオブジェクトの型
/// @tparam Alignment アライメント(バイト)
template <class Type, size_t Alignment = alignof(Type)>
class Allocator
{
public:
static_assert(!std::is_const_v<Type>, "Allocator<const Type> is ill-formed.");
using value_type = Type;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using propagate_on_container_move_assignment = std::true_type;
using is_always_equal = std::true_type;
static constexpr size_t alignment = Alignment;
SIV3D_NODISCARD_CXX20
constexpr Allocator() noexcept = default;
SIV3D_NODISCARD_CXX20
constexpr Allocator(const Allocator&) noexcept = default;
// ↓↓↓変更ここから↓↓↓
// template <class Other>
// SIV3D_NODISCARD_CXX20
// constexpr Allocator(const Allocator<Other>&) noexcept {}
template <class Other, size_t OtherAlignment>
SIV3D_NODISCARD_CXX20
constexpr Allocator(const Allocator<Other, OtherAlignment>&) noexcept {}
// ↑↑↑変更ここまで↑↑↑
[[nodiscard]]
# if SIV3D_PLATFORM(WINDOWS)
__declspec(allocator)
# endif
Type* allocate(size_t n);
void deallocate(Type* p, size_t);
// ↓↓↓追加ここから↓↓↓
template <class U>
struct rebind
{
using other = Allocator<U, Alignment>;
};
// ↑↑↑追加ここまで↑↑↑
};
// ↓↓↓変更ここから↓↓↓
// template <class T1, class T2>
// [[nodiscard]]
// inline constexpr bool operator ==(const Allocator<T1>&, const Allocator<T2>&) noexcept;
template <class T1, size_t A1, class T2, size_t A2>
[[nodiscard]]
inline constexpr bool operator ==(const Allocator<T1, A1>&, const Allocator<T2, A2>&) noexcept;
// ↑↑↑変更ここまで↑↑↑
/// @brief メモリアライメントを考慮したデフォルトのアロケータ
/// @tparam Type アロケーションするオブジェクトの型
template <class Type>
struct DefaultAllocator
{
using type = std::conditional_t<IsOverAligned_v<Type>, Allocator<Type>, std::allocator<Type>>;
};
// ==========
// Allocator.ipp
// ==========
template <class Type, size_t Alignment>
Type* Allocator<Type, Alignment>::allocate(const size_t n)
{
return AlignedMalloc<Type, Alignment>(n);
}
template <class Type, size_t Alignment>
void Allocator<Type, Alignment>::deallocate(Type* const p, const size_t)
{
AlignedFree(p);
}
// ↓↓↓変更ここから↓↓↓
// template <class T1, class T2>
// inline constexpr bool operator ==(const Allocator<T1>&, const Allocator<T2>&) noexcept
template <class T1, size_t A1, class T2, size_t A2>
inline constexpr bool operator ==(const Allocator<T1, A1>&, const Allocator<T2, A2>&) noexcept
// ↑↑↑変更ここまで↑↑↑
{
return true;
}
}
void Main()
{
Array<int32, improved::Allocator<int32, 16>> a{ 1, 2, 3 };
Print << a;
while (System::Update());
}
https://github.com/Siv3D/OpenSiv3D/issues/889#issuecomment-1240576603 の実装案のほう、pull-request で受け入れます! @yaito3014 さんもありがとうございます!