Metaprogramming Academy: Type based referencing

The Partridge Family were neither partridges nor a family. Discuss.
Post Reply
User avatar
cyboryxmen
Posts: 190
Joined: November 14th, 2014, 2:03 am

Metaprogramming Academy: Type based referencing

Post by cyboryxmen » November 17th, 2017, 11:22 am

Morning class!

Today, we'll be going into the fun things you can do with templates! Now even though Chili has already covered the basics of templates, the world of template metaprogramming is far more vast than one video can cover! Today, we are going to explore ways templates can simplify code through type based referencing. Let's say that you want to create a Graphics class that can make different graphic objects and return their ids to the user. Likewise, those graphic objects can be returned to be reused by other users. Without any template metaprogramming, it'll usually look like so:

Code: Select all

class Graphics
{
public:
	using Id = size_t;

	class Sprite
	{
	public:
		Sprite ( ) = default;

		void Render ( ) const
		{
			std::cout << "Sprite rendered!" << '\n';
		}
	};

	class Text
	{
	public:
		Text ( ) = default;

		void Render ( ) const
		{
			std::cout << "Text rendered!" << '\n';
		}
	};

	Graphics ( ) = default;

	Id CreateSprite ( )
	{
		//recycle unused sprites
		if ( unused_sprites_.empty ( ) )
		{
			const auto id = sprites_.size ( );

			sprites_.emplace_back ( );

			return id;
		}
		else
		{
			const auto id = unused_sprites_.back ( );
			unused_sprites_.pop_back ( );

			return id;
		}
	}
	Id CreateText ( )
	{
		//recycle unused texts
		if ( unused_texts_.empty ( ) )
		{
			const auto id = texts_.size ( );

			texts_.emplace_back ( );

			return id;
		}
		else
		{
			const auto id = unused_texts_.back ( );
			unused_texts_.pop_back ( );

			return id;
		}
	}

	void DeleteSprite ( const Id id )
	{
		//save the sprite for reuse
		unused_sprites_.push_back ( id );
		//reset the sprite
		sprites_ [ id ] = { };
	}

	void DeleteText ( const Id id )
	{
		//save the text for reuse
		unused_texts_.push_back ( id );
		//Reset the text
		texts_ [ id ] = { };
	}

	Sprite& GetSprite ( const Id id )
	{
		return sprites_ [ id ];
	}

	Text& GetText ( const Id id )
	{
		return texts_ [ id ];
	}

private:
	std::vector<Sprite> sprites_;
	std::vector<Id> unused_sprites_;
	std::vector<Text> texts_;
	std::vector<Id> unused_texts_;
};
As you can see, there is a lot of boilerplate code to write just to add another type that Graphics can create. Most of the code for each type is practically the same without much deviations. At first, it would seem easy to express these functions using one function template like:

Code: Select all

template<typename Type>
Create( );
...and you would be right except we need a way for the template function to use the vector [sprites_] for [Type = Sprite] and [texts_] for [Type = text]. Fortunately, C++ has an in built class that allows us to do this easily.

Introducing: Tuples!

Code: Select all

std::tuple<int, double, std::string> tuple = std::make_tuple ( 12, 3., "#LeninDidNothingWrong" );

std::cout << std::get<int> ( tuple ) << '\n';
std::cout << std::get<double> ( tuple ) << '\n';
std::cout << std::get<2> ( tuple ) << '\n';
You can think of them as a fancy struct for template metaprogramming. Unlike structs though, members of a tuple can be referenced through indices or types(if the type is unique in the tuple that is) rather than names. This makes them perfect for our current situation. With tuples, along with the help of a helper class...

Code: Select all

template<typename Resource>
class ResourceManager
{
public:
	using ResourceContainer = std::vector<Resource>;
	using Id = typename ResourceContainer::size_type;
	using IdContainer = std::vector<Id>;

	ResourceManager ( ) = default;

	Id Create ( )
	{
		//recycle unused resources
		if ( unused_resources_.empty ( ) )
		{
			const auto id = resources_.size ( );

			resources_.emplace_back ( );

			return id;
		}
		else
		{
			const auto id = unused_resources_.back ( );
			unused_resources_.pop_back ( );

			return id;
		}
	}

	void Delete ( const Id id )
	{
		//save the resource for reuse
		unused_resources_.push_back ( id );
		//reset the resource
		resources_ [ id ] = { };
	}

	Resource& Get ( const Id id )
	{
		return resources_ [ id ];
	}

private:
	ResourceContainer resources_;
	IdContainer unused_resources_;
};
We can redesign the Graphics class with a few more Graphic Objects added in.

Code: Select all

class Graphics
{
public:
	class Sprite
	{
	public:
		Sprite ( ) = default;

		void Render ( ) const
		{
			std::cout << "Sprite rendered!" << '\n';
		}
	};

	class Text
	{
	public:
		Text ( ) = default;

		void Render ( ) const
		{
			std::cout << "Text rendered!" << '\n';
		}
	};

	class Mesh
	{
	public:
		Mesh ( ) = default;

		void Render ( ) const
		{
			std::cout << "Mesh rendered!" << '\n';
		}
	};

	class Isosurface
	{
	public:
		Isosurface ( ) = default;

		void Render ( ) const
		{
			std::cout << "Isosurface rendered!" << '\n';
		}
	};

	Graphics ( ) = default;

	template<typename Type>
	auto Create ( )
	{
		return std::get<ResourceManager<Type>> ( resource_managers_ ).Create ( );
	}

	template<typename Type>
	void Delete ( const typename ResourceManager<Type>::Id id )
	{
		std::get<ResourceManager<Type>> ( resource_managers_ ).Delete ( id );
	}

	template<typename Type>
	auto& Get ( const typename ResourceManager<Type>::Id id )
	{
		return std::get<ResourceManager<Type>> ( resource_managers_ ).Get ( id );
	}

private:
	std::tuple<
		ResourceManager<Sprite>,
		ResourceManager<Text>,
		ResourceManager<Mesh>,
		ResourceManager<Isosurface>
	> resource_managers_;
};
Admittedly, this example is very bare bones and there are more ways to simplify it(having an RAII class to delete the resource automatically for one) but I hope that it shows you the power of tuples. Tuples are the lifeblood of template metaprogramming(and metaprogramming in general really) and you'll learn to love them as you try more and more template metaprogramming techniques. This is merely the start of my lessons and as we continue on, I hope all of you would learn to use more template metaprogramming in your code.
Zekilk

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Metaprogramming Academy: Type based referencing

Post by albinopapa » November 17th, 2017, 7:10 pm

Code: Select all

   template<typename Type>
   auto& Get ( const typename ResourceManager<Type>::Id id )
   {
      return std::get<ResourceManager<Type>> ( resource_managers_ ).Get ( id );
   }
Perhaps an explanation as to why you have: typename in the function parameter list, that's not familiar to most outside of template parameter list arguments. I believe it has something to do with hinting to the compiler that you are accessing a type when using the scope resolution operator:: ( ResourceManager<Type>::Id ), instead of accessing a value.

I admit that template metaprogramming is useful and the STL wouldn't be available without it, but it's harder to reason about and harder to debug since the errors come from the templates even when the error is originally in the code being passed to a template function for instance. So many times have I passed arguments to std::make_unique only to get errors in xmemory.h saying no constructor takes those arguments. Then I have to search the entire project to find all the places I used make_unique just to figure out where I went wrong.

I've been messing around with templates a bit lately, and I have found them to be useful when writing libraries mostly. I have used templates as you do in the Graphics class, creating a common interface ( template functions ) for multiple types. I have tried using std::enable_if(_t), and unfortunately I wasn't able to get the desired result.

Here's a snippet

Code: Select all

// Aliases for comptr objects holding WIC types
// I have others in the project as well, but this is all the types considered bitmap sources.
using WICBitmap = Microsoft::WRL::ComPtr<IWICBitmap>;
using WICBitmapClipper = Microsoft::WRL::ComPtr<IWICBitmapClipper>;
using WICFormatConverter = Microsoft::WRL::ComPtr<IWICFormatConverter>;
using WICBitmapFlipRotator = Microsoft::WRL::ComPtr<IWICBitmapFlipRotator>;
using WICBitmapFrameDecode = Microsoft::WRL::ComPtr<IWICBitmapFrameDecode>;

// Helpers/Traits for types that can be passed as a bitmap source
template<class BitmapResource>
struct is_bitmap_source
{
	// Default case for everything not specialized
	static constexpr bool value = false;
};
template<> struct is_bitmap_source<WICBitmap>
{
	static constexpr bool value = true;
};
template<> struct is_bitmap_source<WICBitmapClipper>
{
	static constexpr bool value = true;
};
template<> struct is_bitmap_source<WICFormatConverter>
{
	static constexpr bool value = true;
};
template<> struct is_bitmap_source<WICBitmapFlipRotator>
{
	static constexpr bool value = true;
};
template<> struct is_bitmap_source<WICBitmapFrameDecode>
{
	static constexpr bool value = true;
};
template<> struct is_bitmap_source<WICBitmapScaler>
{
	static constexpr bool value = true;
};

	// Wanted to give compiler error if object passed wasn't a bitmap source.
	template<class BitmapSource>
	WICFormatConverter CreateWICFormatConverter(
		const std::enable_if_t<is_bitmap_source<BitmapSource>::value, BitmapSource>& Resource,
		const REFWICPixelFormatGUID WicPixelFormat,
		const WICBitmapDitherType DitherType,
		const WICPalette& WicPalette,
		const double AlphaThreshHoldPercent,
		const WICBitmapPaletteType PaletteType,
		const WICImagingFactory& Factory )const
	{
		WICFormatConverter pConverter;

		HRESULT result = Factory->CreateFormatConverter( &pConverter );
		if( FAILED( result ) )
			throw std::runtime_error( "Failed to create format converter." );

		result = pConverter->Initialize(
			Resource.Get(),
			GUID_WICPixelFormat32bppPRGBA,
			WICBitmapDitherTypeNone,
			nullptr,
			0.f,
			WICBitmapPaletteTypeCustom
		);
		if( FAILED( result ) )
			throw std::runtime_error( "Failed to convert bitmap to specified format." );

		return pConverter;
	}
This is what I wanted to do:

Code: Select all

	const WICFormatConverter pConverter = wic.CreateWICFormatConverter(
		pFrame, GUID_WICPixelFormat32bppPRGBA, WICBitmapDitherTypeNone, 
		WICPalette(), 0., WICBitmapPaletteTypeCustom, pFactory );
This is what I had to do:

Code: Select all

	const WICFormatConverter pConverter = wic.CreateWICFormatConverter<WICBitmapFrameDecode>(
		pFrame, GUID_WICPixelFormat32bppPRGBA, WICBitmapDitherTypeNone, 
		WICPalette(), 0., WICBitmapPaletteTypeCustom, pFactory );
For some reason, template parameter deduction/resolution wouldn't work. Compiler kept telling me that no overload of the function existed even when I used one of the ones listed as is_bitmap_source<BitmapSource>::value = true. I tried this on VS2017, but not VS2015. I don't think it would have made a difference.

In the end, I gave up on enable_if for now. I just use the template parameter and the compiler will still tell me if the pointers are convertible or not, so it's a wash. I get what I want, but didn't learn how to use enable_if and my static conditions were a waste of time.

I'm using the windows libraries (Win32, d3d, wic, etc) as testing grounds for learning how to use templates, constexpr and if constexpr. I'm not comfortable using it in game projects, or am just too dumb to figure out where it would be useful.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

User avatar
chili
Site Admin
Posts: 3948
Joined: December 31st, 2011, 4:53 pm
Location: Japan
Contact:

Re: Metaprogramming Academy: Type based referencing

Post by chili » November 18th, 2017, 10:18 am

albinopapa wrote: Perhaps an explanation as to why you have: typename in the function parameter list, that's not familiar to most outside of template parameter list arguments. I believe it has something to do with hinting to the compiler that you are accessing a type when using the scope resolution operator:: ( ResourceManager<Type>::Id ), instead of accessing a value.
Yeah, sort of what you assumed. You need typename there because you are using a type that dependent on a type which is a template parameter.
Chili

User avatar
cyboryxmen
Posts: 190
Joined: November 14th, 2014, 2:03 am

Re: Metaprogramming Academy: Type based referencing

Post by cyboryxmen » November 18th, 2017, 10:39 am

The typename keyword is because of 2 phase lookups that templates go through. Before the template can be fully checked for errors in the second phase where the type parameters are given, it does a quick syntax error check in the first phase with the template itself without any known type parameters. The problem with the first phase check is that the compiler doesn't always know if a name refers to a type. For example,

Code: Select all

Type::Int * x;
Type is an unknown type parameter and Int is one of its members. Since Type is unknown, the compiler is unaware if Type::Int refers to a type or a variable. It could be an int variable making this statement a multiplication of ints. Otherwise, it could be a type in which case, this statement creates a Type::Int* name x. Because of this ambiguity, the C++ Committee has decreed that dependent names like Type::Int are assumed to always be variables or functions unless told otherwise. This is what typename is for.

You might wonder why the compiler can't easily interpret this through context and usage. In fact, why have a 2 phase look-up at all? If you get rid of the first phase, this madness wouldn't be needed. It's really not my place to tell the Committee how to do their job. Though I have to say, there are a lot of bullshit involving templates back in its early years(For one, std::vector<std::vector<int>> used to be standardized to give out errors because >> must always be interpreted as a right bit shift!)
Zekilk

User avatar
chili
Site Admin
Posts: 3948
Joined: December 31st, 2011, 4:53 pm
Location: Japan
Contact:

Re: Metaprogramming Academy: Type based referencing

Post by chili » November 19th, 2017, 8:25 am

cyboryxmen wrote:The typename keyword is because of 2 phase lookups that templates go through. Before the template can be fully checked for errors in the second phase where the type parameters are given, it does a quick syntax error check in the first phase with the template itself without any known type parameters. The problem with the first phase check is that the compiler doesn't always know if a name refers to a type. For example,

Code: Select all

Type::Int * x;
Type is an unknown type parameter and Int is one of its members. Since Type is unknown, the compiler is unaware if Type::Int refers to a type or a variable. It could be an int variable making this statement a multiplication of ints. Otherwise, it could be a type in which case, this statement creates a Type::Int* name x. Because of this ambiguity, the C++ Committee has decreed that dependent names like Type::Int are assumed to always be variables or functions unless told otherwise. This is what typename is for.
Nice explanation!
Chili

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Metaprogramming Academy: Type based referencing

Post by albinopapa » November 19th, 2017, 10:18 am

Awesome, thanks
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Post Reply