OpenSiv3D icon indicating copy to clipboard operation
OpenSiv3D copied to clipboard

Allocator をコンテナのアロケータとして使えるようにする

Open Raclamusi opened this issue 1 year ago • 3 comments

発生している問題

s3d::Arraystd::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::Allocatorstd::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());
}

Raclamusi avatar Sep 08 '22 09:09 Raclamusi

非型テンプレート引数を持たないような実装以外に、 Cpp17Allocator の制約に基づいて適切な typename Allocator::template rebind<U>::other を提供する方法もあると思われます。

yaito3014 avatar Sep 08 '22 10:09 yaito3014

ご指摘ありがとうございます。 その方法であれば、 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());
}

Raclamusi avatar Sep 08 '22 11:09 Raclamusi

https://github.com/Siv3D/OpenSiv3D/issues/889#issuecomment-1240576603 の実装案のほう、pull-request で受け入れます! @yaito3014 さんもありがとうございます!

Reputeless avatar Sep 08 '22 13:09 Reputeless