Jump to content

  • Log In with Google      Sign In   
  • Create Account

Passing function template pointer


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
6 replies to this topic

#1 Juliean   GDNet+   -  Reputation: 2606

Like
0Likes
Like

Posted 13 August 2014 - 03:43 PM

Hello,

 

I want to be able to pass a pointer to a templated function as a paramter of another function, without specifying the concret template type. From what I've read, this is not possible per-se, however since my case is very specific, I belive that there is at least a work-around. This is how it should syntactically work and look like:

// template function to be passed & called
template<typename Type>
void appendAttribute(ICustomComponentAttributes::AttributeVector& vAttributes, const xml::Node& value)
{
    vAttributes.emplace_back(value.GetName(), conv::FromString<Type>(value.GetValue()));
}

// this is the receiving function/method
template<typename Function, typename Return, typename... Args> // ???
Return AttributeDeclaration::CallByType(Args&&... args) const
{
    switch(type)
    {
    case AttributeType::BOOL:
        return Function<bool>(args...);
    case AttributeType::FLOAT:
        return Function<float>(args...);
    case AttributeType::INT:
        return Function<int>(args...);
    case AttributeType::STRING:
        return Function<std::wstring>(args...);
    default:
        ACL_ASSERT(false);
    }
}

// in code

attribute.CallByType<appendAttribute>(vAttributes, node);

So as you can see, what I already try is to pass the function as part of a template, which of course didn't work that easily. I searched around a bit and found template template arguments, which unfortunately didn't seem to work for my example.

 

Is there a way to get this to work with a similar way to what I try to achieve with template arguments here? I belive that there is, since technically the template instantiations are known inside the CallByType-method. If there isn't, is there any workaround? Code complexity is of no concern, but it should be as easy to use in the actual code as possible, since this will be used in many instances. Thanks!

 

 

 

 

 

 

 

 



Sponsor:

#2 ApochPiQ   Moderators   -  Reputation: 15698

Like
2Likes
Like

Posted 13 August 2014 - 04:14 PM

Why does CallByType have to accept appendAttribute as a template parameter?

The usual way of doing this kind of thing in C++ is to pass the appendAttribute function as a regular function parameter into CallByType, which then invokes it via () syntax. This can be templated to allow for arbitrary functions, function objects, and so on to be passed into CallByType.

#3 Juliean   GDNet+   -  Reputation: 2606

Like
0Likes
Like

Posted 13 August 2014 - 04:24 PM


Why does CallByType have to accept appendAttribute as a template parameter?

 

I just used a template parameter because I believed that this kind of stuff could not be done by passing the function template as a function parameter. Please by any means correct me if I'm wrong but there is no such thing as:

void function(void (*func)<typename type>(void))
{
}

Or is there? The thing is that I don't explicitely want to instantiate the templated function in the user code. I want the CallByType-method to actually create the instantiations... which, in my limited knowledge of the inner working of things here lead to the assumption that this could only be done by passing the function as the template itself.

 

If it works the way you described it, could you give me a short (pseudo)code example of how I would use it in that way? I can't quite wrap my head around it :/



#4 ApochPiQ   Moderators   -  Reputation: 15698

Like
2Likes
Like

Posted 13 August 2014 - 04:40 PM

Think of it this way: your function template as posted will generate overloads of the function appendAttribute, each with the same function signature but a different body. To wit, the function signature is:

void foo(ICustomComponentAttributes::AttributeVector&, const xml::Node&)
Now, part two of the equation:

template <typename FType>
void InvokeFunction (FType func) {
    func(42);
}
Calling InvokeFunction(foo); for any function foo (well, as long as it accepts something convertible from int) will call through to foo with the parameter 42.

Since the appendAttribute function signature is known, you can do the same trick. Consider this minimal program:

#include <iostream>

template <typename T>
void Invoked (int param) {
    std::cout << param << std::endl;
}

template <typename FType>
void Invoker (FType func) {
    func(42);
}

int main () {
    Invoker(Invoked<int>);
}
I'm not entirely clear on what you want your attribute append code to look like, but those are the building blocks; hopefully that gets you to a solution, but if not, if you could clarify your intent a bit I'd be happy to try and piece together something.

#5 Juliean   GDNet+   -  Reputation: 2606

Like
0Likes
Like

Posted 14 August 2014 - 02:28 AM


I'm not entirely clear on what you want your attribute append code to look like, but those are the building blocks; hopefully that gets you to a solution, but if not, if you could clarify your intent a bit I'd be happy to try and piece together something.

 

I quess it needs a little more explanation. I always try to keep things as simple as possible when explaining, but I quess I failed here.

 

So basically I want to simplify/encapsulate one specific instance of code that I'm using quite often in a specific module. In that module, there is a class called "AttributeDeclaration" which holds a type-enumerator.

enum class AttributeType
{
    INT, FLOAT, BOOL, STRING
}

Now in specific code, I have to switch based on this attribute-type in order to work on some data based on the AttributeDeclaration, which on first iteration would look like this:

void Loader::InitCustomAttribute(const xml::NodeVector& vNodes, ICustomComponentAttributes& custom)
{
	ICustomComponentAttributes::AttributeMap mAttributes;
	mAttributes.reserve(vNodes.size());

	for(auto pValue : vNodes)
	{
		const auto type = (AttributeType)pValue->Attribute(L"type")->AsInt();
		
		// here, we switch on the type and convert the string to a specific known "POD" type.
		switch(type)
		{
		case AttributeType::BOOL:
			mAttributes.emplace(value.GetName(), conv::FromString<bool>(value.GetValue()));
			break;
		case AttributeType::INT:
			mAttributes.emplace(value.GetName(), conv::FromString<int>(value.GetValue()));
			break;
		case AttributeType::FLOAT:
			mAttributes.emplace(value.GetName(), conv::FromString<float>(value.GetValue()));
			break;
		case AttributeType::STRING:
			mAttributes.emplace(value.GetName(), conv::FromString<std::wstring>(value.GetValue()));
			break;
		}
	}

	custom.OnSetAttributes(*pComponent, mAttributes);
}

Note that there is a good amount of dublicated code in the switch-case statements, which only really differs by the template-argument for the FromString-function (lets pretend for the sake of simplicity that conv::FromString<std::wstring> was a real thing). So it appears natural to make a template helper function:

template<typename Type>
void appendAttribute(ICustomComponentAttributes::AttributeMap& mAttributes, const xml::Node& value)
{
	mAttributes.emplace(value.GetName(), conv::FromString<Type>(value.GetValue()));
}

void Loader::InitCustomAttribute(const xml::NodeVector& vNodes, ICustomComponentAttributes& custom)
{
	ICustomComponentAttributes::AttributeMap mAttributes;
	mAttributes.reserve(vNodes.size());

	for(auto pValue : vNodes)
	{
		const auto type = (AttributeType)pValue->Attribute(L"type")->AsInt();

                // now at least the implementation of each switch-block is unified
		switch(type)
		{
		case AttributeType::BOOL:
			appendAttribute<bool>(mAttributes, *pValue);
			break;
		case AttributeType::INT:
			appendAttribute<int>(mAttributes, *pValue);
			break;
		case AttributeType::FLOAT:
			appendAttribute<float>(mAttributes, *pValue);
			break;
		case AttributeType::STRING:
			appendAttribute<std::wstring>(mAttributes, *pValue);
			break;
		}
	}

	custom.OnSetAttributes(*pComponent, mAttributes);
}

Well, now I have the implementation for each data-type shared. Thats the way it looks almost everywhere I "have to" (see at the end of the post) switch on the type. Still, I always have to write the switch-statement per hand for every usage of the attribute-declaration, which causes

 

- dublicated code

- loss in readability due to increased code length (subjective)

- prone to errors (more than once did I use <float> on AttributeType::INT)

 

And thats where my original question kicks in. Thats also why your example isn't usable for me (unless I`m mistaken in comprehending it), because

Invoker(Invoked<int>);

I can`t/won't call my appendAttribute template-function with the specific type in the "user" code. It should essentially look like this:

// how exactly is this declared?
// for sake of clarity, attributes are hard-coded for the appendAttribute-function here
void CallByType(AttributeType type, ICustomComponentAttributes::AttributeMap& mAttributes, const xml::Node& value)
{
	// we moved the switch inside of this function - now its unified & encapsulated,
	// all the user needs to know is that the correct overload of the template function will be
	// called
	switch(type)
	{
	case AttributeType::BOOL:
		inFunction<bool>(mAttributes, *pValue);
		break;
	case AttributeType::INT:
		inFunction<int>(mAttributes, *pValue);
		break;
	case AttributeType::FLOAT:
		inFunction<float>(mAttributes, *pValue);
		break;
	case AttributeType::STRING:
		inFunction<std::wstring>(mAttributes, *pValue);
		break;
	}
}

void Loader::InitCustomAttribute(const xml::NodeVector& vNodes, ICustomComponentAttributes& custom)
{
	ICustomComponentAttributes::AttributeMap mAttributes;
	mAttributes.reserve(vNodes.size());

	for(auto pValue : vNodes)
	{
		const auto type = (AttributeType)pValue->Attribute(L"type")->AsInt();
		
		// now we got rid of the switch-statement entirely.
		CallByType<appendAttribute>(type, mAttributes, *pValue);
	}

	custom.OnSetAttributes(*pComponent, mAttributes);
}

So the CallByType-function should execute this one very template functions correct overload based on the runtime-resolved variable. Which means it has to instantiate all 4 template declarations - but thats part of what this function should do, not what I want to do everytime I use the CallByType-function.

 

Then, whenever I have to execute code that appends on an AttributeType-variable, I can call this function with a template overload, which will save me a lot of unnecessary code dublication.

 

Did I manage to get the idea across more clearly this time? If so, I'm glad to hear any suggestion you might have, otherwise please let me know again.

 

PS: Regarding the whole switch-statement per se. I get the feeling your first instinct is calling "use polymorphism, dumbass". I just wanted to add that thats not a real option here for various reasons. The range of operations that are executed depending on such a type-enum is way to differential as for me to be able to put it in one, or even a few well-defined classes. So before I have to create a factory/interface/4 implementations for every such simple template-helper functions, I much prefer the template function with addition of the switch-helper I proposed. You might suggest that based on that the whole system is flawed, but I'm actually really OK with how it works out, minus having to type that switch-statement over and over. Well, thats what this thread is hopefully going to solve.


Edited by Juliean, 14 August 2014 - 02:31 AM.


#6 ApochPiQ   Moderators   -  Reputation: 15698

Like
1Likes
Like

Posted 14 August 2014 - 04:24 PM

This is a really simple mockup of how you could proceed:

#include "stdafx.h"
#include <iostream>
#include <string>

enum Type {
    TYPE_INT,
    TYPE_STRING,
    TYPE_FLOAT,
};


void Printer (int value) {
    std::cout << "INT: " << value << std::endl;
}

void Printer (const std::string & value) {
    std::cout << "STR: " << value.c_str() << std::endl;
}

void Printer (float value) {
    std::cout << "FLT: " << value << std::endl;
}


template <typename T>
struct PrinterWrapper {
    void operator () (void * valuePtr) {
        Printer(*reinterpret_cast<const T *>(valuePtr));
    }
};


template <template <typename FuncParamT> class FuncT>
void InvokeByType (Type t, void * valuePtr) {
    switch (t) {
    case TYPE_INT:
        FuncT<int>()(valuePtr);
        break;

    case TYPE_STRING:
        FuncT<std::string>()(valuePtr);
        break;

    case TYPE_FLOAT:
        FuncT<float>()(valuePtr);
        break;
    }
}


int main () {
    int intValue = 42;
    std::string strValue = "Test";
    float fltValue = 3.1415f;

    InvokeByType<PrinterWrapper>(TYPE_INT, &intValue);
    InvokeByType<PrinterWrapper>(TYPE_STRING, &strValue);
    InvokeByType<PrinterWrapper>(TYPE_FLOAT, &fltValue);
}
Obviously the void* is kinda lame, but you can easily replace that with passing through your XML node and using appropriate parser functions to go from the node attributes to the target types.

I *think* that solves your situation :-)

#7 Juliean   GDNet+   -  Reputation: 2606

Like
0Likes
Like

Posted 14 August 2014 - 05:30 PM


This is a really simple mockup of how you could proceed:

 

Ah, I see, using a class to "pass" the function in, clever. That does what I need, but I think I can make it even a little simplier or just my needs:

class getStringFromOffset
{
public:
    template<typename Type>
    static std::wstring Call(const BaseComponent& component, size_t offset)
    {
        return conv::ToString(*(const Type*)((const char*)(&component) + offset));
    }
};

template<typename Functor, typename Return, typename... Args>
Return callByType(AttributeType type, Args&&... args)
{
    switch(type)
    {
    case AttributeType::BOOL:
        return Functor::Call<bool>(args...);
    case AttributeType::FLOAT:
        return Functor::Call<float>(args...);
    case AttributeType::INT:
        return Functor::Call<int>(args...);
    case AttributeType::STRING:
        return Functor::Call<std::wstring>(args...);
    default:
        ACL_ASSERT(false);
    }
}

std::wstring attributeToString(const AttributeDeclaration& attribute, const BaseComponent& component)
{
    return attribute.CallByType<getStringFromOffset, std::wstring>(component, attribute.offset);
}

While also making the call more generic by using templates for return & args. Having such a void* would also kind of go against the principle of what I'm trying to achieve, since contrary to your example the correct TYPE will always be determined by a variable and therefore I won't have any TYPE-specific stuff I have to pass in - thats actually the function/classes task, like you said by e.g. parsing an XML-node to the correct data format.

 

So the only thing that kind of annoying is having to create a class for every function I have to use that way. Do you maybe know any way to simplify this even further and probably allow me to skip the whole class declaration thing? It can even make he callByType-function horribly cluttered, as long as declaring & calling it is as easy as possible ;) I already tried doing a specific macro, but that looked just horrible (additionally to how much I hate macros).

#define TYPE_FUNCTOR(name, ret, args, body)	\
struct name							\
{									\
	template<typename Type>			\
	static ret Call args			\
	{								\
		body						\
	}								\
};									\

TYPE_FUNCTOR(getStringFromOffset, std::wstring, (const BaseComponent& component, size_t offset),
	return conv::ToString(*(const Type*)((const char*)(&component) + offset));
)

So, if you have any idea how to maybe make this work still by only declaring the function w/o that class declaration, it would be really cool - maybe some kind of helper-class template magic? Don't know, if there isn't any way, its still workable, just a little inconvience for me. Thanks regardless, helped me a lot already ;)


Edited by Juliean, 14 August 2014 - 05:32 PM.





Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS