Long time no see and Input Capture

The Partridge Family were neither partridges nor a family. Discuss.
cameron
Posts: 794
Joined: June 26th, 2012, 5:38 pm
Location: USA

Re: Long time no see and Input Capture

Post by cameron » November 21st, 2018, 12:43 am

Ok, I think I'm on the right track. The point of the function class to is store any callable as you describe but with a common interface. std::function works for all standard function pointers; you have to change the declaration in order to get it to accept member functions. So, I wrap the function pointer call in a lambda and pass that to the std::function constructor. The idea here is to overload based on callable type to avoid overhead. Not sure how to test if the callable is a lambda though. Suppose I could make an overload for all non-function pointers. I'd still like to figure out why that code block above won't compile though.

Code: Select all

template <typename FUNC>
struct is_function_ptr
	: 
	std::integral_constant<bool, std::is_pointer<FUNC>::value &&
	std::is_function<typename std::remove_pointer<FUNC>::type>::value>
{};

template<typename RT, typename... Args>
class Function
{
public:
	Function(void* p = nullptr) {}

	template<typename FUNC, typename = std::enable_if_t<is_function_ptr<FUNC>::value>>
	Function(FUNC&& func)
		: action([func](Args&&... args)->RT {return (*func)(std::forward<Args>(args)...); })
	{}

	template<typename FUNC, typename O, typename = std::enable_if_t<std::is_member_function_pointer<FUNC>::value>>
	Function(FUNC&& func, O* o)
		: action([func, o](Args&&... args)->RT {return (o->*func)(std::forward<Args>(args)...); })
	{}

	template<typename FUNC, typename O, typename = std::enable_if_t<std::is_member_function_pointer<FUNC>::value>>
	Function(FUNC&& func, O& o)
		: action([func, &o](Args&&... args)->RT {return (o.*func)(std::forward<Args>(args)...); })
	{}
Computer too slow? Consider running a VM on your toaster.

cameron
Posts: 794
Joined: June 26th, 2012, 5:38 pm
Location: USA

Re: Long time no see and Input Capture

Post by cameron » November 21st, 2018, 1:54 am

Ok this appears to be working.

Code: Select all

template<typename RT, typename... Args>
class Function
{
public:
	template<typename FUNC>
	Function(FUNC&& func, typename std::enable_if_t<!is_function_ptr<FUNC>::value>* = nullptr)
		: action([func](Args&&... args)->RT {return func(std::forward<Args>(args)...); })
	{}

	template<typename FUNC>
	Function(FUNC&& func, typename std::enable_if_t<is_function_ptr<FUNC>::value>* = nullptr)
		: action([func](Args&&... args)->RT {return (*func)(std::forward<Args>(args)...); })
	{}

	template<typename FUNC, typename O, typename = std::enable_if_t<std::is_member_function_pointer<FUNC>::value>>
	Function(FUNC&& func, O* o)
		: action([func, o](Args&&... args)->RT {return (o->*func)(std::forward<Args>(args)...); })
	{}

	template<typename FUNC, typename O, typename = std::enable_if_t<std::is_member_function_pointer<FUNC>::value>>
	Function(FUNC&& func, O& o)
		: action([func, &o](Args&&... args)->RT {return (o.*func)(std::forward<Args>(args)...); })
	{}

	operator bool() const
	{
		return action.operator bool();
	}

	RT operator()(Args... args) const
	{
		return action(std::forward<Args>(args)...);
	}

private:
	std::function<RT(Args...)> action;
};
Computer too slow? Consider running a VM on your toaster.

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

Re: Long time no see and Input Capture

Post by albinopapa » November 21st, 2018, 2:20 am

Code: Select all

template <typename FUNC>
struct is_function_ptr
   : 
   std::integral_constant<bool, std::is_pointer<FUNC>::value &&
   std::is_function<typename std::remove_pointer<FUNC>::type>::value>
{};
could also be

Code: Select all

template <typename FUNC>
struct is_function_ptr : 
   std::conditional_t<
      std::conjunction_t<
         std::is_pointer<FUNC>,
         std::is_function<std::remove_pointer_t<FUNC>>
      >, 
      std::true_type, 
      std::false_type
   >
{};
In this instance, std::conditional_t chooses either std::true_type or std::false_type based on the result of the std::conjunction ( template meta-programming AND type ).

Using the _t versions, you get to bypass having to type: typename std::attribute::type;
When using std::conjunction template meta-programming AND or std::disjunction template meta-programming OR, you can't use the _v versions, it expecting types, not values.
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: Long time no see and Input Capture

Post by albinopapa » November 21st, 2018, 2:54 am

Wow!, still using single letters for parameter types and names?

Anyway, nice job figuring that out. I tried some things on my end using std::variant instead of std::function to keep type information...didn't go so well.
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

cameron
Posts: 794
Joined: June 26th, 2012, 5:38 pm
Location: USA

Re: Long time no see and Input Capture

Post by cameron » November 21st, 2018, 4:12 am

Yeah, ik... my naming conventions arent standard pre-se but it works for me I guess lol. Its kinda strange because many of my university classes thus far want different naming conventions.

Anyways, thanks. I didn't know about the conjunction/disjunction stuff, tho I did know about the _t variants for type traits.

Do you have any clue why that previous code I psoted wont compile? Ill paste it again because its on the last page now.

Code: Select all

enum CallingConvention { CDECLR, STDCALL, FASTCALL, THISCALL };
template<CallingConvention> struct CCHelper;

template<> struct CCHelper<CDECLR>
{
	template<typename RT, typename... Args>
	using PFunc = RT(__cdecl *)(Args...);

	template<typename RT, typename O, typename... Args>
	using PFuncM = RT(__cdecl O::*)(Args...);

	template<typename RT, typename O, typename... Args>
	using PFuncMC = RT(__cdecl O::*)(Args...) const;
};
template<> struct CCHelper<STDCALL>
{
	template<typename RT, typename... Args>
	using PFunc = RT(__stdcall *)(Args...);

	template<typename RT, typename O, typename... Args>
	using PFuncM = RT(__stdcall O::*)(Args...);

	template<typename RT, typename O, typename... Args>
	using PFuncMC = RT(__stdcall O::*)(Args...) const;
};
template<> struct CCHelper<FASTCALL>
{
	template<typename RT, typename... Args>
	using PFunc = RT(__fastcall *)(Args...);

	template<typename RT, typename O, typename... Args>
	using PFuncM = RT(__fastcall O::*)(Args...);

	template<typename RT, typename O, typename... Args>
	using PFuncMC = RT(__fastcall O::*)(Args...) const;
};
template<> struct CCHelper<THISCALL>
{
	template<typename RT, typename... Args>
	using PFunc = RT(__thiscall *)(Args...);

	template<typename RT, typename O, typename... Args>
	using PFuncM = RT(__thiscall O::*)(Args...);

	template<typename RT, typename O, typename... Args>
	using PFuncMC = RT(__thiscall O::*)(Args...) const;
};
This block here wont compile:

Code: Select all

template<CallingConvention CC, typename RT, typename... Args>
using PFunc = typename CCHelper<CC>::PFunc<RT, Args...>;

template<CallingConvention CC, typename RT, typename O, typename... Args>
using PFuncM = typename CCHelper<CC>::PFuncM<RT, O, Args...>;

template<CallingConvention CC, typename RT, typename O, typename... Args>
using PFuncMC = typename CCHelper<CC>::PFuncMC<RT, O, Args...>;
Attachments
Capture.PNG
(54.77 KiB) Not downloaded yet
Computer too slow? Consider running a VM on your toaster.

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

Re: Long time no see and Input Capture

Post by albinopapa » November 21st, 2018, 8:58 am

Oh, to answer your earlier question about why I avoid dynamic polymorphism. I did some tests in the past and found that virtual function calls aren't that expensive, so that's not it just to get that out of the way. It's due to the individual allocations of each derived class and not being next to each other in memory. Granted, CRTP doesn't avoid this, but allows you to have static dispatch. So if you combined CRTP with something like std::variant, then you could get data locality and static dispatch.

Check this out:

Code: Select all

100 iterations of 10,000,000 elements in 3 different vectors.  Displayed results are averaged: sum_time / num_iterations

// In this run, the static polymorphism used if constexpr instead of a runtime if to determine if it could
// cast to derived type and call a function that was a part of the Base class.
Dynamic polymorphism test result: 0.192118
Mixed polymorphism test result: 0.125081
Static polymorphism test result: 0.074151 

// In this run, the static polymorphism used the runtime if just like the other two tests
Dynamic polymorphism test result: 0.177580
Mixed polymorphism test result: 0.126362
Static polymorphism test result: 0.119815

All the tests were adding and multiplying some vectors and a little branching.

I'm really quite shocked here, I feel as though the dynamic polymorphism did quite a bit better than expected.

Seems the biggest improvement can be had if you can use constexpr if to determine unique behavior not apart of a base interface.

if the Static polymorphism result is 100%, the Mixed would be about 105% slower and the Dynamic would be about 148% slower most other things being equal ( runtime if branching ). However, being able to determine at compile time what type you are dealing with, either using the visitor pattern or a constexpr if, the Mixed is 168% slower and the Dynamic is 259% slower.

The original goal was to check on data locality, but it seems this test wasn't sufficient enough to make a solid determination.

Honestly, here's what I'm taking away form this.

Dynamic polymorphism is slow. It prohibits data locality and if you need to know the specific type outside of the derived class, no matter which route you take, branching could be more of an issue. In this run, I cast directly to the derived type after checking it's enum type.

Mixed polymorphism, as I'm calling it, using std::variant to promote data locality, and some templated base class that is templated on the derived class for some static dispatch can help some. In most cases, if you aren't branching to determine an objects base type, it's just as fast as Static polymorphism because data locality is the dominant factor. In these tests, I casted to the templated base class that was derived class matching it's enum type.

Static polymorphism using std::variant for data locality and the C++ type system is king when needing to know when you can access unique data or member functions.

There is also the old visitor pattern that I didn't try, where you pass the base object to a visitor object member function, this gets you a derived visitor. Then you pass the derived visitor to a member function on the original object, this gets you the derived object. Then you can call whatever function you need from there. I know, that's confusing, that's why I never used it, but I guess in all fairness, it might be worth a test.
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: Long time no see and Input Capture

Post by albinopapa » November 21st, 2018, 9:00 am

Well, why: CallingConvention CC? Template parameters either have to be declared
type parameter
template<typename Type>

or non-type parameter
template<int32_t N>
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: Long time no see and Input Capture

Post by albinopapa » November 21st, 2018, 9:02 am

maybe should be:
template<typename CallingConvention, typename ReturnType, typename...Args>
using PFunc = typename CCHelper<CallingConvention>::PFunc<RT, Args...>;
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

cameron
Posts: 794
Joined: June 26th, 2012, 5:38 pm
Location: USA

Re: Long time no see and Input Capture

Post by cameron » November 21st, 2018, 8:06 pm

Interesting information there. I haven't really found a use for 'pure' static polymorphism unless you count template specialization. I think in the long run where a variant makes sense, use a variant, where polymorphism makes sense, use polymorphism. I've started focusing less on optimization type stuff and prefer cleaner and more readable code. Most of the optimization comes into play with the algorithm used I've found, though other things such as data locality and SSE/AVX can certainly help too. Though, each has its own place for sure.

CallingConvention is an enum type so it wouldn't really have mattered whether I typed int CC or CallingConvention CC, it's a non-type template parameter. You cannot use a type because calling conventions are not types.
Computer too slow? Consider running a VM on your toaster.

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

Re: Long time no see and Input Capture

Post by albinopapa » November 21st, 2018, 9:22 pm

This part is new to me, but this might actually be the issue:

Code: Select all

template<CallingConvention CC, typename RT, typename... Args>
using PFunc = CCHelper<CC>::template PFunc<RT, Args...>;
The second part of that expression is a template, so it might require declaring it as template.


It's funny you say "I've started focusing less on optimization type stuff and prefer cleaner and more readable code."

I wish I could get off this kick, maybe it would help me focus on actually producing something.

It's not entirely that I am obsessed with performant code, it's just that I'm way too curious about all the different ways one thing can be done. If it's faster great I'll go with it, if not I keep looking.

You are right about algorithms being the best way to keep your program quick, and SIMD instructions as well as multi-threading are great as well. For me, std::variant is something new for me to learn and play with, and am finding different ways of using and implementing it so that maybe I can figure out a CLEAN way of using it. My first few naive approaches have ended in poorly written templated hell. I tried creating an ECS library using std::variant, but ran myself in circles.

To me, templates allow for polymorphic behavior with the exception of being able to have a container of different types. This and the limitations of not knowing, but needing to know the underlying type to handle unique interactions between two or more objects. Collision handling for instance between a Circle and a Rectangle. You may have a base class called Shape, but then how to get the underlying types?

You either use dynamic_cast until you find the right types
You have a list of identifiers like enums that you can check and static_cast or reinterpret_cast
You can use double dispatch bouncing between functions until the two types are resolved

Even with that, you still have the issue of storage.

You can create a custom allocator, and allocate from it so that each item is local to the other
You can create a single interface that has multiple containers backing the data

I admit, using dynamic polymorphism and some sort of identifier to cast or even dynamic casting is way simpler to deal with and it makes for a clean and simple interface.
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