Playing with templates and type traits

The Partridge Family were neither partridges nor a family. Discuss.
albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Playing with templates and type traits

Post by albinopapa » September 7th, 2018, 9:33 pm

For a study into template meta-programming I decided to take on the Direct3D library.

As an example, one of the interfaces needed while setting up the render pipeline is the DepthStencilView ( ID3D11DepthStencilView ). It requires a DXGI_FORMAT and there are only five supported formats for creating a ID3D11DepthStencilView interface.
I could write something like:

Code: Select all

	// Helper trait to determine if format is supported as a depth buffer format
	template<DXGI::Format _format> struct is_supported_depth_buffer_view_format : std::false_type {};
	template<> struct is_supported_depth_buffer_view_format<DXGI::Format::Unknown> : std::true_type {};
	template<> struct is_supported_depth_buffer_view_format<DXGI::Format::D16_UNorm> : std::true_type {};
	template<> struct is_supported_depth_buffer_view_format<DXGI::Format::D24_UNorm_S8_UInt> : std::true_type {};
	template<> struct is_supported_depth_buffer_view_format<DXGI::Format::D32_Float> : std::true_type {};
	template<> struct is_supported_depth_buffer_view_format<DXGI::Format::D32_Float_S8X24_UInt> : std::true_type {};

	// Syntactic sugar else: is_supported_depth_buffer_view<_format>::value
	template<DXGI::Format _format>
	constexpr bool is_supported_depth_buffer_view_format_v =
		is_supported_depth_buffer_view_format<_format>::value;

	// Default all formats as not supported
	template<typename T, DXGI::Format _format>
	struct is_supported_format : std::false_type {};
	
	// Specialize on T using previously defined support traits for type T
	template<DXGI::Format _format> struct is_supported_format<DepthStencilView, _format> : 
		std::conditional_t<is_supported_depth_buffer_view_format_v<_format>, std::true_type, std::false_type> {};
	
	// Syntactic sugar else: is_supported_format<T, _format>::value
	template<typename T, DXGI::Format _format>
	constexpr bool is_supported_format_v = is_supported_format<T, _format>::value;
The is_supported_format structure takes two template parameters, the wrapper class I am creating for an ID3D11DepthStencilView interface. It inherits from std::false_type by default.
The is_supported_depth_buffer_view struct deals directly with the DepthStencilView so only takes the format enum as it's template parameter. It is defined as false by default and true for each of the supported types by inheritance. If I pass in a DXGI::Format::Unknown as the parameter, the is_supported_depth_buffer_view<DXGI::Format::Unknown> struct inherits from std::true_type and inherits the static constexpr bool value of true.

So, how to use this. You have to create a DepthStencilViewDesc struct to pass to the DepthStencilView constructor. So the DepthStencilViewDesc will be templated and the template variables can be check at compile time and will cause a compile time error if the incorrect format is passed in.

Code: Select all

template<DXGI::Format _format>
struct DepthStencilViewDesc
{
	static_assert( is_supported_depth_stencil_view_format_v<_format>,
		"Invalid format used for DepthStencilViewDesc, supported DXGI::Format formats:\n"
		"Unknown, D16_UNorm, D24_UNorm_S8_UInt, D32_Float and D32_Float_S8X24_UInt" );

	DepthStencilViewDesc()=default;
	DepthStencilViewDesc( const D3D11_DEPTH_STENCIL_VIEW_DESC& _source );
	operator D3D11_DEPTH_STENCIL_VIEW_DESC() const;

	DXGI::Format Format;
	DSVDimension ViewDimension;
	DSVFlag Flags;
	union
	{
		Tex1DDSV Texture1D;
		Tex1DArrayDSV Texture1DArray;
		Tex2DDSV Texture2D;
		Tex2DArrayDSV Texture2DArray;
		Tex2DMultisampleDSV Texture2DMS;
		Tex2DMultisampleArrayDSV Texture2DMSArray;
	};	
};
Without this trait, the user could pass in anything ( including random numbers since the Direct3D api uses normal enums which are implicitly convertible to int ) and the call to create the ID3D11DepthStencilView would fail. With this trait in place, you are required to use the enum class DXGI::Format and if you pass in an invalid format, you get a compile time error with an error message that is a little more informative.

I'm sure I'll be able to use the is_supported_format version at some point, but I haven't gone completely generic programming yet. I want to be able to go as generic as I can and only rely on the type system. There are features, formats and other aspects that are only available using later versions of Direct3D, but where possible I'd like to be able to reuse code and allow for static polymorphism ( as shown in the traits code above ).

I still have a few things to learn when it comes to template meta-programming, but I think I'm on the right track. If anyone is interested, I am mostly learning from reading through the standard library and stackoverflow. There are a few basic tutorials on YouTube, there might be more advanced ones also, but I haven't looked yet.
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

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

Re: Playing with templates and type traits

Post by albinopapa » September 10th, 2018, 9:51 pm

Code: Select all

#include <cassert>
#include <intrin.h>

struct sse_integral_tag {};
struct sse_floating_tag {};
struct sse_double_tag {};
struct avx_integral_tag {};
struct avx_floating_tag {};
struct avx_double_tag {};
struct avx512_integral_tag {};
struct avx512_floating_tag {};
struct avx512_double_tag {};


template<typename>struct simd_tag_trait {};
template<typename Tag>
constexpr bool is_sse_v =
std::is_same_v<Tag, sse_integral_tag> ||
std::is_same_v<Tag, sse_floating_tag> ||
std::is_same_v<Tag, sse_double_tag>;

template<typename Tag> constexpr bool is_avx_v =
std::is_same_v<Tag, avx_integral_tag> ||
std::is_same_v<Tag, avx_floating_tag> ||
std::is_same_v<Tag, avx_double_tag>;

template<typename Tag> constexpr bool is_avx512_v =
std::is_same_v<Tag, avx512_integral_tag> ||
std::is_same_v<Tag, avx512_floating_tag> ||
std::is_same_v<Tag, avx512_double_tag>;

template<typename Tag> constexpr bool is_simd_integral_v =
std::is_same_v<Tag, sse_integral_tag> ||
std::is_same_v<Tag, avx_integral_tag> ||
std::is_same_v<Tag, avx512_integral_tag>;

template<typename Tag> constexpr bool is_simd_float_v =
std::is_same_v<Tag, sse_floating_tag> ||
std::is_same_v<Tag, avx_floating_tag> ||
std::is_same_v<Tag, avx512_floating_tag>;

template<typename Tag> constexpr bool is_simd_double =
std::is_same_v<Tag, sse_double_tag> ||
std::is_same_v<Tag, avx_double_tag> ||
std::is_same_v<Tag, avx512_double_tag>;


template<typename Tag>
struct tag_trait
{
	using type = std::conditional_t<
		std::is_same_v<Tag, sse_integral_tag>,
		__m128i, std::conditional_t<
		std::is_same_v<Tag, sse_floating_tag>,
		__m128, std::conditional_t<
		std::is_same_v<Tag, sse_double_tag>,
		__m128d, std::conditional_t<
		std::is_same_v<Tag, avx_integral_tag>,
		__m256i, std::conditional_t<
		std::is_same_v<Tag, avx_floating_tag>,
		__m256, std::conditional_t<
		std::is_same_v<Tag, avx_double_tag>,
		__m256d, std::conditional_t<
		std::is_same_v<Tag, avx512_integral_tag>,
		__m512i, std::conditional_t<
		std::is_same_v<Tag, avx512_floating_tag>,
		__m512, std::conditional_t<
		std::is_same_v<Tag, avx512_double_tag>,
		__m512d, void>>>>>>>>>;

	using load_type = std::conditional_t<
		is_simd_integral_v<Tag>,
		type*, std::conditional_t<
		is_simd_float_v<Tag>,
		float*, std::conditional_t<
		is_simd_double<Tag>,
		double*, void>>>;

	static constexpr size_t element_count = is_sse_v<Tag> ?
		size_t( 4 ) : is_avx_v<Tag> ?
		size_t( 8 ) : is_avx512_v<Tag> ?
		size_t( 16 ) : size_t( 1 );
	
};


template<typename ArrT, typename SimdTag>
class simd_iterator
{
public:
	using iterator_category		= std::random_access_iterator_tag;
	using value_type			= typename tag_trait<SimdTag>::type;
	using pointer				= typename tag_trait<SimdTag>::type*;
	using reference				= typename tag_trait<SimdTag>::type&;
	using difference_type		= std::ptrdiff_t;
public:
	simd_iterator() = default;
	explicit simd_iterator( ArrT* _parent )
		:
		simd_iterator( &( *_parent )[0], _parent )
	{
	}
	explicit simd_iterator( typename ArrT::value_type* _ptr, ArrT* _parent )
		:
		parent( _parent ),
		ptr( reinterpret_cast< pointer >( _ptr ) )
	{

	}

	reference operator*()
	{
		return *ptr;
	}
	reference operator[]( size_t _idx ) { return *( ( *this ) + _idx ); }

	simd_iterator& operator++()
	{
		++ptr;
		return *this;
	}
	simd_iterator operator++( int )
	{
		auto result = *this;
		++( *this );
		return result;
	}
	simd_iterator& operator--()
	{
		--ptr;
		return *this;
	}
	simd_iterator  operator--( int )
	{
		const auto result = *this;
		--( *this );
		return result;
	}

	simd_iterator& operator+=( difference_type _offset )
	{
		ptr += _offset;
		return *this;
	}
	simd_iterator& operator-=( difference_type _offset )
	{
		return ( *this ) += ( -_offset );
	}
	simd_iterator  operator+( difference_type _offset )const
	{
		return simd_iterator( *this ) += _offset;
	}
	simd_iterator  operator-( difference_type _offset )const
	{
		return simd_iterator( *this ) -= _offset;
	}
	difference_type operator-( const simd_iterator& _other )const
	{
		return std::ptrdiff_t( ptr - _other.ptr );
	}

	bool operator==( const simd_iterator& _other )const
	{
		return ptr == _other.ptr;
	}
	bool operator!=( const simd_iterator& _other )const
	{
		return !( ( *this ) == _other );
	}
	bool operator<(  const simd_iterator& _other )const
	{
		return ptr < _other.ptr;
	}
	bool operator>(  const simd_iterator& _other )const
	{
		return ptr > _other.ptr;
	}
	bool operator<=( const simd_iterator& _other )const
	{
		return ptr <= _other.ptr;
	}
	bool operator>=( const simd_iterator& _other )const
	{
		return ptr >= _other.ptr;
	}

private:
	pointer ptr;
	ArrT* parent;
};


template<typename ArrT,typename SimdTag>
auto make_simd_iterator( size_t _idx, ArrT& _arr, SimdTag )->
std::enable_if_t<
	std::is_same_v<
		typename ArrT::iterator::iterator_category,
		std::random_access_iterator_tag>,
	simd_iterator<ArrT, SimdTag>>
{
	const auto size = _arr.size();
	const auto last = size & ~( tag_trait<SimdTag>::element_count - 1 );
	const auto idx = std::min( _idx, last );
	auto ptr = ( &_arr[ 0 ] ) + idx;
	return simd_iterator<ArrT, SimdTag>( ptr, &_arr );
}	
Usage:

Code: Select all

	// Alias so I only have to change the tag in one place instead of three
	using simd_tag = sse_floating_tag;

	// Call make_simd_iterator with 
	//  * the index you want the iterator to start from
	//  * the container you want to use (buffer1 and buffer2 are std::vector<float>
	//  * the simd tag that your want to to use
	// Tags are:
	//  * sse_integral_tag, sse_floating_tag, sse_double_tag
	//  * avx_integral_tag, avx_floating_tag, avx_double_tag
	//  * avx512_integral_tag, avx512_floating_tag, avx512_double_tag

	const auto num_elements = 1'000'000;
	auto beg1 = make_simd_iterator( 
		size_t( 0 ), buffer1, simd_tag()
		);
	auto end1 = make_simd_iterator(
		numElements, buffer1, simd_tag()
		);
	auto beg2 = make_simd_iterator(
		size_t( 0 ), buffer2, simd_tag()
		);

	auto sse_dot_product = []( const __m128& lhs, const __m128& rhs )
	{
		auto mul = _mm_mul_ps( lhs, rhs );
		auto x = _mm_shuffle_ps( mul, mul, _MM_SHUFFLE( 0, 0, 0, 0 ) );
		auto y = _mm_shuffle_ps( mul, mul, _MM_SHUFFLE( 1, 1, 1, 1 ) );
		auto z = _mm_shuffle_ps( mul, mul, _MM_SHUFFLE( 2, 2, 2, 2 ) );
		auto w = _mm_shuffle_ps( mul, mul, _MM_SHUFFLE( 3, 3, 3, 3 ) );
		x = _mm_add_ps( x, y );
		z = _mm_add_ps( z, w );

		return _mm_add_ps( x, z );
	};
	auto sse_or_combine = []( const __m128& mask, const __m128& if_true, __m128& if_false )
	{
		auto temp1 = _mm_and_ps( mask, if_true );
		auto temp2 = _mm_andnot_ps( mask, if_false );
		return _mm_or_ps( temp1, temp2 );
	};
	auto sse_normalize = [ & ]( const __m128& lhs, const __m128& )
	{	
		auto sqDist = sse_dot_product( lhs, lhs );
		auto dist = _mm_sqrt_ps( sqDist );
		auto is_zero = _mm_cmpeq_ps( dist, _mm_setzero_ps() );

		auto result = _mm_div_ps( lhs, dist );		
		result = sse_or_combine( is_zero, _mm_setzero_ps(), result );
		return result;
	};

	// works with standard algorithms
	std::transform( beg1, end1, beg2, beg2, sse_normalize );
So, this happened. I finally made my first working STL compatible iterator and decided to try it with SIMD support. Given that SIMD instructions operator on multiple elements at a time ( 4, 8 or 16 ) the make_simd_iterator function makes sure that if the index is not a multiple of one of these values, you won't go passed the end. For example, if the length of the vector was 999, the end would be cut to 996 for SSE, 992 for AVX and 984 for AVX512. When the iterator reaches the address at one of these indices, the STL algorithm will exit and you have to handle the remaining cases.

I could have make_simd_iterator() return an std::pair, with the first element as the iterator and the second as the index where you need to handle. An alternative would be to come up with a simd_algorithm library where you must pass in the iterators and two callable objects, one for handling the simd portion and one for the remaining portion you must handle using x86 instructions if any remainder exists.
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

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

Re: Playing with templates and type traits

Post by albinopapa » September 11th, 2018, 4:19 am

One of the requirements of the simd_iterator is the container must support random access such as std::vector. The requirement is enforced here:

Code: Select all

std::enable_if_t<                                  // enable_if_t is used to enable/disable overloads of functions 
	std::is_same_v<                                 // is_same is used to check if two types are the same
	typename ArrT::iterator::iterator_category,     // stl containers have mostly uniform aliases to help 
	                                                // guide the developers when template programming
	std::random_access_iterator_tag>,               // iterator tag that tells the compiler what functions 
	simd_iterator<ArrT, SimdTag>>                   // can use this iterator and what functionality it might have
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: Playing with templates and type traits

Post by chili » September 11th, 2018, 5:43 am

Oh I love me some custom iterators...

Actually, I believe that many of the std lib algorithms support vectorization and MT in C++17 or maybe C++20 with additional execution policy flags.

Your template explorations look pretty interesting btw, good stuff.
Chili

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

Re: Playing with templates and type traits

Post by albinopapa » September 11th, 2018, 9:06 am

chili wrote:Actually, I believe that many of the std lib algorithms support vectorization and MT in C++17 or maybe C++20 with additional execution policy flags.
I spent some time looking through the STL in Visual Studio and can't seem to find where the execution policies are located. Some stuff online said to look in <experimental/parallel> I think, but there isn't a parallel file. The STL algorithms do have template parameters for execution policies, but I can't seem to find where they are located. Not only that, the std::transform overloads with the execution policy template parameters for instance just call the regular versions, so I really don't think they are completely implemented yet.
chili wrote:Your template explorations look pretty interesting btw, good stuff.
Thanks man. I seem to have more fun pretending to be a library creator than a game developer. Perhaps someday I will actually make something others will find useful. For now I suppose I just keep coding and learning until then.
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

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

Re: Playing with templates and type traits

Post by albinopapa » September 11th, 2018, 9:28 am

Honestly, I know that the purpose of creating a library is to make things simpler, but I think that's why I do it. I enjoy taking the APIs that I learn and finding simpler ways of of using them. The Direct3D API isn't terribly difficult, but the learning curve is kind of steep in my opinion. There are a shit ton of options that affect the output ( if there is any ), so finding a way to minimize errors or simply creating something that is quick to get up and running.

Starting from scratch to get a 3D scene up and rendering even a cube takes me like an hour or so because of all the boiler plate code. Sometimes you just want to be able to test something out instead of muddling up your project, but it's not so simple with D3D. I usually find myself running back to the chili framework to test out as much as possible, but it's not going to be exact because it's 2D.

I suppose some of that changes with the 3D Fundamentals framework now, good job on finding a nice effective way of handling different shaders. Honestly, I think that inspired me to start looking into template meta-programming. Can't wait to see what else you have planned.

I would like to make a request if I may.

DESIGN PATTERNS - PLEASE?

ECS, Observer, Visitor. For the life of me, I can't seem to make heads or tails of this shit. I suppose ECS is actually something I got close to understanding, but I can't figure out how different entities are suppose to communicate ( hence the mention of Observer and Visitor patterns as well ). Object interactions has been the bane of my coding existence since the beginning. I don't know why, but I want to use the Visitor pattern over the class holding an enum or some other ID, nor do I want to rely on RTTI. Surely, something like the Visitor pattern would be more elegant.

I think I have found a nifty trick with std::variant, but I'll have to test it out. I'll post here after testing and explain.
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

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

Re: Playing with templates and type traits

Post by albinopapa » September 12th, 2018, 7:10 am

Ok, here it is.

The helper functions

Code: Select all

#pragma once

#include <variant>

template<typename T, typename Callable>
void visit_unary( T& _variant, Callable&& _callable )
{
	std::visit( _callable, _variant );
}
template<typename T, typename Callable>
void visit_binary( T&& _variant1, T&& _variant2, Callable&& _callable )
{
	std::visit( [ & ]( auto& v1 )
		{
			std::visit( [ & ]( auto& v2 )
				{
					_callable( v1, v2 );
				}, _variant2 );
		}, _variant1 );
}
The class hierarchy

Code: Select all

struct Rect { float left, top, right, bottom; };
struct Circle { Vec2 center; float radius; };

class Base
{
public:
	Base() = default;
	const Vec2& Position()const
	{
		return position;
	}
	void Position( Vec2 _position )
	{
		position = _position;
	}

protected:
	Vec2 position = { 200.f,500.f };
};
class Player : public Base
{
public:
	void Update( const float _dt ){ position += ( velocity*_dt ); }
	const Vec2& Velocity()const{ return velocity; }
	void Velocity( Vec2 _velocity ){ velocity = _velocity; }
	void TakeDamage( const float _amount ) { health -= _amount; }

	Rect GetBounding()const { return rect; };
	float DealDamage()const { return 10.f; }
private:
	Vec2 velocity = { 200.f, 0.f };
	float health = 100.f;
	Rect rect;
};
class Enemy : public Base
{
public:
	void Update( Player& _player, const float _dt ){ FightPlayer( _player ); }
	void TakeDamage( const float _amount ) { health -= _amount; }

	Circle GetBounding()const { return circle; }
	float DealDamage()const { return 15.f; }

private:
	void FightPlayer( Player& _player )
	{
		_player.Velocity( _player.Velocity() - Vec2{ 10.f, 0.f } );
	}

private:
	Circle circle;
	float health = 100.f;
};
The dispatchers, and a lonely helper FindPlayer function

Code: Select all

template<typename VariantVector> Player& FindPlayer( VariantVector& variants );
struct Collidable
{
	static bool IsColliding( const Rect& _lRect, const Rect& _rRect )
	{
		return
			_lRect.left <= _rRect.right && _lRect.right >= _rRect.left &&
			_lRect.top <= _rRect.bottom && _lRect.bottom >= _rRect.top;
	}
	static bool IsColliding( const Rect& _rect, const Circle& _circle )
	{
		return
			( _rect.left - _circle.center.x ) < _circle.radius ||
			( _rect.top - _circle.center.y ) < _circle.radius ||
			( _rect.right - _circle.center.x ) < _circle.radius ||
			( _rect.bottom - _circle.center.y ) < _circle.radius;
	}
	static bool IsColliding( const Circle& _circle, const Rect& _rect )
	{
		return IsColliding( _rect, _circle );
	}
	static bool IsColliding( const Circle& _lCircle, const Circle& _rCircle )
	{
		auto sq = []( float val ) { return val * val; };
		const auto dx = sq( _lCircle.center.x - _rCircle.center.x );
		const auto dy = sq( _lCircle.center.y - _rCircle.center.y );
		const auto combRad = sq( _lCircle.radius ) + sq( _rCircle.radius );
		return ( dx + dy ) <= combRad;
	}
};

template<typename VariantVector> Player& FindPlayer( VariantVector& variants );

struct Updateable
{
	Updateable( std::vector<std::variant<Player, Enemy>>& _variants )
		:
		variants( _variants )
	{
	}
	void operator()( Player&_player )
	{
		_player.Update( 1.f );
	}
	void operator()( Enemy&_enemy )
	{
		_enemy.Update( FindPlayer( variants ), 1.f );
	}

	std::vector<std::variant<Player, Enemy>>& variants;
};

template<typename VariantVector>
Player& FindPlayer( VariantVector& variants )
{
	Player* pPlayer = nullptr;
	for( auto& variant : variants )
	{
		std::visit( [ & ]( auto& object )
			{
				if constexpr( std::is_same_v<std::decay_t<decltype( object )>, Player> )
				{
					pPlayer = &object;
				}
			}, variant );

		if( pPlayer ) return *pPlayer;
	}

	throw std::bad_variant_access();
}
The logic functions

Code: Select all

void UpdateObjects( std::vector<std::variant<Player, Enemy>>& _variants )
{
	for( auto& variant : _variants )
	{
		visit_unary( variant, Updateable( _variants ) );
	}
}
void CheckCollisions( std::vector<std::variant<Player, Enemy>>& _variants )
{
	for( size_t i = 0; i < _variants.size(); ++i )
	{
		for( size_t j = i + 1; j < _variants.size(); ++i )
		{
			visit_binary( _variants[ i ], _variants[ j ], 
				[]( auto& _obj1, auto& _obj2 )
				{
					if( Collidable::IsColliding( _obj1.GetBounding(), _obj2.GetBounding() ) )
					{
						_obj1.TakeDamage( _obj2.DealDamage() );
						_obj2.TakeDamage( _obj1.DealDamage() );
					}
				} );
		}
	}
}
Finally, the initialization and call code

Code: Select all

	std::vector<std::variant<Player, Enemy>> variants;
	
	variants.emplace_back( Player() );
	for( int i = 0; i < 2; ++i )
	{
		variants.emplace_back( Enemy() );
	}
	
	UpdateObjects( variants );
	CheckCollisions( variants );
I thought this was going to be a simple task, but I had to run some experiments and turns out variants are quite picky. This excursion has made me very tired, so it might be a day or two before I incorporate a working demo and get it up on GitHub.
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

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

Re: Playing with templates and type traits

Post by albinopapa » September 12th, 2018, 7:24 am

Just to be completely transparent, I got the idea from cppreference.com on the std::variant page. If it weren't for the examples on their, I don't think I would have ever thought of this or even figured out how to use variants.
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: Playing with templates and type traits

Post by chili » September 16th, 2018, 6:46 pm

variant is something I would like to try when I run into the situation where I want polymorphism, but i need my data to be contiguous (for cache prefetching reasons). And I think the visit stuff looks fairly sexy (though there are detractors from the implementation that the comittee ultimately went with).
Chili

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

Re: Playing with templates and type traits

Post by albinopapa » September 17th, 2018, 7:52 am

(though there are detractors from the implementation that the comittee ultimately went with).
Do tell...
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