0

I have a group of functions which perform similar action on different input parameter classes ParamX, ParamY, ParamZ. They have a general form like:

Result funcX(ParamX param){
  
  auto result = param.getX(); // param.getY() in case of ParamY
  
  // check result is valid 
  if(!result.valid()){
     // print a log message for ParamX
     return {};
  }
  
  return result.value();
}

Result is a templated struct like Result<ParamX>, based on the type of input parameter class ParamX.

Also note that each param will call a completely different function param.getX(), param.getY(), param.getZ()

So given I have several different functions funcX, funcY, funcZ, each acting on ParamX, ParamY, ParamZ separately, how do I convert this into a single template function?

Also note that in the future, any developer can make another ParamA, ParamB .... class with different getA(), getB() methods, so nothing is fixed.

revolutionary
  • 3,314
  • 4
  • 36
  • 53
  • So are `X`/`Y`/`Z` types or integer values? In what kind of template argument do you want to translate them? – user17732522 Apr 03 '22 at 04:53
  • @user17732522 ParamX, ParamY, ParamZ are different classes e.g. it could be a Server class that makes connection to a server to get result, or it could be DiskFile class that looks for the result on the disk. – revolutionary Apr 03 '22 at 13:22

3 Answers3

1

given I have several different functions funcX, funcY, funcZ, each acting on ParamX, ParamY, ParamZ separately, how do I convert this into a single template function?

You can make use of function templates and auto type deduction(using the auto type specifier in the return type) as shown below:

template<typename ParamX>
//--vvvv---------------------->auto type deduction
    auto funcX(ParamX param){
  
        auto result = param.getX();
  
        // check result is valid 
        if(!result.valid()){
         // print a log message for ParamX
         // add appropriate return statement if needed
        }
      
        return result.value();
}

Edit

Now since you've edited your question to specify that each of the passed arguments will have their own getter, you can use the following code.

Solution 1

Here we make use of pointer to member function as the second argument to the function template.

#include <iostream>
#include<string>

struct Name 
{
    bool valid()
    {
        return true;
    }
    int value ()
    {
        return 5;
    }
};
struct Server1
{
  Name getX()
  {
      std::cout<<"getX called"<<std::endl;
      return Name();
  }
};
struct Server2 
{
    Name getY()
    {
        std::cout<<"getY called"<<std::endl;
        return Name();
    }
};

template<typename ParamX, typename RType>
//--vvvv---------------------->auto type deduction
    auto funcX(ParamX param,RType (ParamX::*callable)()){
        std::cout<<"funcX called"<<std::endl;
        
        auto result = ((param.*callable)());
  
        // check result is valid 
        if(!result.valid()){
         // print a log message for ParamX
         // add appropriate return statement if needed
        }
      
        return result.value();
}
int main()
{
    funcX(Server1(), &Server1::getX); 
    
    funcX(Server2(), &Server2::getY);
    return 0;
}

Demo

The output of the above is:

funcX called
getX called
funcX called
getY called

Solution 2

This is a variation of solution 1 above. Here the second argument of the function template is taken as a reference to const.


#include <iostream>
#include<map>
#include<string>
#include<vector>
struct Name 
{
    bool valid()
    {
        return true;
    }
    int value ()
    {
        return 5;
    }
};
struct Server1
{
  Name getX()
  {
      std::cout<<"getX called"<<std::endl;
      return Name();
  }
};
struct Server2 
{
    Name getY()
    {
        std::cout<<"getY called"<<std::endl;
        return Name();
    }
};

template<typename ParamX, typename Callable>
//--vvvv---------------------->auto type deduction
    auto funcX(ParamX param, const Callable& callable){
        std::cout<<"funcX called"<<std::endl;
        
        auto result = (param.*callable)();
  
        // check result is valid 
        if(!result.valid()){
         // print a log message for ParamX
         // add appropriate return statement if needed
        }
      
        return result.value();
}
int main()
{
    funcX(Server1(), &Server1::getX);
    
    funcX(Server2(), &Server2::getY);
    return 0;
}

Demo

Jason
  • 36,170
  • 5
  • 26
  • 60
  • Please note: that param.getX() call will be different for each different paramter. It will be param.getY(), param.getZ() for ParamX and ParamZ. So these are completely unrelated functions that i also want to templatize – revolutionary Apr 03 '22 at 13:14
  • 1
    @revolutionary This important piece of info was **missing** in the original question. – Jason Apr 03 '22 at 13:18
  • I thought it would be obvious that ParamX can call getX. But ParamY cannot have getX() as it's totally unrelated to ParamX. – revolutionary Apr 03 '22 at 13:20
  • 1
    @revolutionary But it seems that it was not obvious to us all. This is expected because you already know about the code, but we don't. And what may be obvious to you might not be obvious to us. So this is one of the reason why we should give all(as much as we can) details into the original question, specially the important one like this one. – Jason Apr 03 '22 at 13:22
  • Yes, i have added that extra bit of information now. Infact that is the most challenging part of the problem. I'm also trying to generalize in a good way as i cannot provide the original code. – revolutionary Apr 03 '22 at 13:25
  • @revolutionary Are we allowed to add one more argument into the function `funcX`? I mean i have the solution if we're allowed to add one more argument to the function template `funcX`. – Jason Apr 03 '22 at 13:50
  • Yes, in a template method you can do anything, because it will be totally new helper function. And I will refactor the old methods to use the new template calls. – revolutionary Apr 03 '22 at 13:53
  • @revolutionary I have added the solution in my edited answer. Check out my edited answer. [Working Demo](https://onlinegdb.com/fCTLZJAb2) – Jason Apr 03 '22 at 13:57
  • Thanks this looks cool, kind of what I am looking for. Let me check this and I'll let you know – revolutionary Apr 03 '22 at 14:00
  • @revolutionary Sure, take your time. I have also added another solution(as solution 2) at the end of my answer. Solution 2 is just a variation of solution 1 where the second argument is taken as a *reference to const*. [Solution 2 demo](https://www.onlinegdb.com/onNSY3cHK2). – Jason Apr 03 '22 at 14:06
  • @revolutionary Did you check out the answer and did it help? – Jason Apr 06 '22 at 17:48
0

Assuming this was requirements above:

template <typename TParam,typename TResult>

TResult funcX(TParam param){

auto result = param.getX();

// check result is valid 
 if(!result.valid()){
 // print a log message for ParamX
   return {};
}

return result.value();
}

Note if you want to generalize coordinate you can invoke template compile time polymorpism by implying a member(in this case its getx of tparam). If you want to generalize u just need to agree on a name. Alternatively if performance not important u can do runtime polymorpism via a virtual member. This doesnt require a template.

If u want a complicated template example have a look at my non blocking network templates here:

https://bitbucket.org/ptroen/crossplatformnetwork/src/master/OSI/Application/Stub/HTTPSServer/httpsserver.cc

ptroen
  • 21
  • 3
  • Please note that getX() will be a call for ParamX but for ParamY it will be getY(). So somehow these functions I am thinking need to be passed up as function pointers – revolutionary Apr 03 '22 at 13:43
  • You can do this for a template parameter but this is more c like(virtual is better semantically) . Its essentially a template function pointer see this discussion https://stackoverflow.com/questions/1174169/function-passed-as-template-argument – ptroen Apr 03 '22 at 18:51
0

It depends on the properties of ParamX, Result<ParamX>, and the other types to some extent.

But, assuming (since you haven't specified)

  • You have a templated type Result such that Result<ParamX> is the result corresponding to a ParamX, and Result<ParamY> is the result corresponding to a ParamY
  • Result<ParamX> is default constructible (so it will be constructed in return {} for a function that returns Result<ParamX>;
  • ParamX, ParamY, etc all have a member function getX();
  • The return type of that getX() function is implicitly convertible to Result<ParamX>;
  • The return type of that getX() function has a member function named valid() that returns an integral type (where a zero or false return indicates invalid);

You might do something like

template<class Param> Result<Param> func(Param param)
{

    auto result = param.getX();

    // check result is valid 
    if(!result.valid())
    {
       // print a log message
       return {};
    }
    return result.value();
}   

Then, to use that function you might do;

 ParamX  px;
 Result<ParamX> result = Result(px);    // Result(px) is equivalent to Result<ParamX>(px)

 auto result2 = Result(px);     // compiler will deduce the type of result2 as Result<ParamX>

 ParamY  py;
 Result<ParamY> result = Result(py);    // Result(py) is equivalent to Result<ParamY>(py)

Note I'm making a lot of assumptions in the above, to fill in information you have not provided. You need to make a similar set in your code and (more importantly) ensure they are true.

Peter
  • 35,646
  • 4
  • 32
  • 74
  • Please note that the call param.getX() will be different for other parameters i.e. it will be param.getY() for ParamY and for ParamZ it will be param.getZ(). These are completely unrelated methods. So probably some kind of function pointer might be needed ? – revolutionary Apr 03 '22 at 13:17