Because I can...templates

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

Because I can...templates

Post by albinopapa » April 4th, 2019, 7:50 am

Code: Select all

struct TrueType { bool value = true; };
struct FalseType { bool value = false; };
using BoolType = std::variant<TrueType, FalseType>;

// struct choice:	structure to store functors to be called.
// CheckFunc check:	callable/invokable non-member object used to determine if doFunc is to be called
// Func dFunc:		callable/invokable non-member object called if choice::check() return true
template<typename CheckFunc, typename Func>
struct choice
{
	constexpr choice( CheckFunc _check, Func _func )
		:
		check( _check ),
		doFunc( std::move( _func ) )
	{}
	
	CheckFunc check;
	Func doFunc;
};

// template argument deduction guide, helps compiler figure out the template parameters for class types
// without the need for a make_* function
template<typename CheckFunc, typename Func> choice( CheckFunc, Func )->choice<CheckFunc, Func >;

// void choose() calls _cond.check() and if true, runs _cond.doFunc() recursively until 
// no more choices are available or the _cond.doFunc() function has been called.
template<template<typename, typename> typename Cond, typename CheckFunc, typename Func, typename...Rest>
void choose( const Cond<CheckFunc, Func> _cond, Rest&&... _rest )
{
	auto check_condition = [ & ]( auto _condition )
	{
		if constexpr( std::is_same_v<decltype( _condition ), TrueType> )
		{
			_cond.doFunc();
		}
		else if constexpr( sizeof...( Rest ) > 0 )
		{
			choose( std::forward<Rest>( _rest )... );
		}
	};

	std::visit( check_condition, _cond.check() ? BoolType{ TrueType{} } : BoolType{ FalseType{} } );
}
Usage:

Code: Select all

choose(
	choice{ 
	[&]{ return x < 0; }, 	// if condition
	[&]{ x = 0; } },	// do this if condition was true
	choice{ 
	[&]{ return x + width >= ScreenWidth; }, // else if condition
	[&]{ x = ScreenWidth - width - 1;} }	 // do this if this condition was true
);
choose(
	choice{ 
	[&]{ return y < 0; }, 
	[&]{ y = 0; } },
	choice{ 
	[&]{ return y + height >= ScreenHeight; }, 
	[&]{ y = ScreenHeight - height - 1;} }
);
You can add as many 'choice's to the choose function as needed and for an 'else' case, just return true for the first lambda: [&]{return true} and handle the else case. I haven't gotten the hang of std::tuple or more precisely std::integer_sequence yet, so I didn't quite get the ability to pass parameters to the functions working. I did look back over cppreference.com and came across std::apply which if I understand correctly would work perfectly for this situation.

I haven't tested the performance of this, so no guarantees, but I'm going to say it's probably slower than just using if/else if/else blocks. With this it still does the runtime if check for each condition ( just as a the 'if' blocks would ), but it also has to recursively call choose() until all have been 'choice's have been evaluated. It does have an early out though, once it runs the _cond.doFunc() it unwinds and exits.

I have ran tests just to see if it works, and am currently using it in current project and it does seem to work for all I'm using it for.

I think it makes the code look a little nicer and technically you could pass anything with the signature: void(void), meaning a functor with the call operator overloaded that takes no parameters and returns void. While it does work with function pointers with signature: void(*)(void) it does not work with member function pointers since they need a pointer to the class of the member function. I could probably just add another parameter to the template parameter list and use: if constexpr( std::is_member_function_pointer_v<Func> ) or create an overload of the choose function and a separate 'choice' class which would accept a member function and pointer to object.

I probably won't go much further with this though as it is working out for what I'm using it for without extra functionality. The capture list of lambdas makes passing parameters kind of pointless.

The motivation for this was mostly curiosity and to test myself. It seems this is about all I can muster as far as recursion goes. Still, I'm sure I'll find something else that triggers my interest.
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