15

I have a template function, let's call it the "client":

template<typename T>
void client(T (*func)(const std::string&), const std::string& s) {}

Then there are a number of "adaptee" functions that all have an identical type of the first, non-default argument, but the following arguments vary in number and have default values:

void adaptee_one(const std::string&, int i = 1, char* c = nullptr) {}
void adaptee_two(const std::string&, float* f = nullptr) {}

The above functions are a given. Now what I want to do is to pass them to the above client<>() function as the first parameter, and I only care about passing the first argument, const std::string&. So I do the following:

void bindAdapteeOne(const std::string& s) {
    return adaptee_one(s);
}

void bindAdapteeTwo(const std::string& s) {
    return adaptee_two(s);
}

And then pass bindAdapteeX() to client<>().

What I'd like to do is to automate the wrapping or have one (templated) wrapper instead of one per adaptee. I feel this might be the case for variadics, but have little idea about how to apply them exactly.

C++11 is fine, C++14 is fine if absolutely necessary.

Rakete1111
  • 42,521
  • 11
  • 108
  • 141
iksemyonov
  • 3,923
  • 1
  • 19
  • 37
  • Are lambdas allowed? – AndyG Jul 06 '17 at 17:31
  • @AndyG It's in a namespace, outside of any class, so I think yes, as long as there is a sane place to keep them. – iksemyonov Jul 06 '17 at 17:34
  • 1
    @rici Because that [doesn't work](https://wandbox.org/permlink/WYzQfhLbPA7KXIEy). – Barry Jul 07 '17 at 02:00
  • @Barry: Indeed right. Although it works fine with [`operator()`](https://wandbox.org/permlink/WYzQfhLbPA7KXIEy), that's still a bunch of boilerplate. (But there is still something to be said for flexible templating rather than forcing conversion to a function pointer.) – rici Jul 07 '17 at 02:32

3 Answers3

12

C++11 is fine, C++14 is fine if absolutely necessary.

C++11 solution here.

What I'd like to do is to automate the wrapping or have one (templated) wrapper instead of one per adaptee.

I wouldn't do that. You can simply use non-capturing lambdas and let them decay to function pointers:

client (+[](const std::string& s) { return adaptee_one(s); }, "foo");

I don't think that wrapping them in template stuff or whatever would give you a solution that is more readable or easy to use.


As a minimal, working example:

#include<string>

template<typename T>
void client(T (*func)(const std::string&), const std::string& s) {}

void adaptee_one(const std::string&, int i = 1, char* c = nullptr) {}
void adaptee_two(const std::string&, float* f = nullptr) {}

int main() {
    client (+[](const std::string& s) { return adaptee_one(s); }, "foo");
}
skypjack
  • 45,296
  • 16
  • 80
  • 161
  • 2
    Is the "plus" not a typo? I've never seen that before. – iksemyonov Jul 06 '17 at 17:35
  • 6
    @iksemyonov It's a [positive lambda](https://stackoverflow.com/questions/18889028/a-positive-lambda-what-sorcery-is-this) :) Just joking, it is used to convert the lambda into a function pointer with the help of its conversion operator (see the linked question). – Rakete1111 Jul 06 '17 at 17:36
  • Thanks! Holy cow. Glad I posted this question - would've never known instead. – iksemyonov Jul 06 '17 at 17:37
  • 1
    @Rakete1111 ... _Positive lambda_ ... :-D – skypjack Jul 06 '17 at 17:37
  • @Rakete1111 https://stackoverflow.com/questions/18889028/a-positive-lambda-what-sorcery-is-this It is indeed :p – iksemyonov Jul 06 '17 at 17:38
  • Actually, the "function pointer" syntax of `client` is just a consequence of my attempt to solve the problem. You can change the type of that argument into a lambda if required. – iksemyonov Jul 06 '17 at 17:41
  • @iksemyonov It would allow you to use capturing lambdas then. Anyway keep in mind that only non-capturing lambdas decay to function pointers. – skypjack Jul 06 '17 at 17:43
7

This is one of those times where a macro helps:

#define WRAP_FN(f) +[](std::string const& s) -> decltype(auto) { return f(s); }

Though you could just write the body of that inline instead.


There isn't anything else you could do. The problem is that the default arguments aren't visible in the function's signature, so once you get into the type system, there's no way to differentiate:

void works(std::string const&, int=0);
void fails(std::string const&, int  );

Both of those are void(*)(std::string const&, int). So you can't have a function template or class template wrapper - you need to do it inline with a lambda (or a macro that wraps a lambda).

Barry
  • 247,587
  • 26
  • 487
  • 819
  • Thanks! The template solution contains some stuff I have to look up, but, regardless, it looks quite hideous. No way or need to use variadics here to account for the varying number of arguments? – iksemyonov Jul 06 '17 at 17:51
  • @iksemyonov You said you wanted to only pass on the `std::string`, where would you need "varying number of arguments"? – Passer By Jul 06 '17 at 17:53
  • @PasserBy The whole thing with the default arguments? The number of the arguments in the complete set varies. – iksemyonov Jul 06 '17 at 17:54
  • @iksemyonov Then you should edit that in your question – Passer By Jul 06 '17 at 17:55
  • @PasserBy "but the following arguments vary in number and have default values:" – iksemyonov Jul 06 '17 at 17:57
  • @PasserBy OP said they preferred C++11, but yeah `decltype(auto)` would do exactly the same thing here yes. – Barry Jul 06 '17 at 18:00
0

I'm thinking I would create a class that wraps your parameters and have the client accept an instance of that class. That way, you only have one parameter, which contains however many parameters you so desire.

That parameter wrapper would provide the default values as well, and allow you to refine them in derived classes for specific purposes.

This is probably a little more self-documenting as well when compared to lambdas.

And who knows, when its time to read and write the parameters from a file, then the wrapper class would be the perfect place to do it.

Rodney P. Barbati
  • 1,399
  • 17
  • 16