9

I'm a bit rusty in C++ and I'm switching after a year of python. Naturally I would like to translate the laziness coming from python to C++.

I just discovered rot13 and I'm all excited about it. I found 3 ways of doing it and I wanted to do a little performance test. I wanted to see also if there is a difference in modifying the string in place or creating a new one. So I ended up having 6 functions.

In the first method, I use a std::map to map the characters, thus I've built a class that initializes the map, in the second I use a ternary operator, and the third I use a bit shift.

Now the functions prototypes look like this

// map dependent
void Rot13::convert_inplace(string& mystr){

string Rot13::convert(const string& mystr){

// ternary operator
void bitrot_inplace(string& mystr){

string bitrot(const string&  mystr){

// bit shift
void bitshiftrot_inplace(string& mystr){

string bitshiftrot(const string& mystr){

I wanted to construct a function that accept those functions as arguments to then calculate the time and print the results

So I had a look at stackoverflow, 1, 2, and I came up with this

typedef void (*vfc)(string str);

void printtime_inplace(string title, vfc func){

I tried this construction yet this means I'm limited by the vfc return type which in my case is either void or string, and by the fact I need to pass the pointer of the class. Thus I will have to do 3 functions to accommodate the different functions, namely a function for the class member function, a function for the void return type and a function for the string return type.

So I asked myself, is this the case where I really need to use templates to not write 3 times the same function? I'm really not confident with templates but should I do 3 typedefs and structure the printtime function to accept a template? Moreover is there a way to tell the template you will accept only these types (namely the one I defined)?

An other question, is this let's say a good design? or would you suggest an other design? An other implementation?

Pella86
  • 500
  • 3
  • 10
  • 18
  • 2
    So basically you want to benchmark the different implementations? Take a look at ```std::function``` for your benchmarking function. – Freakyy Aug 10 '17 at 12:07
  • 1
    It looks like `Rot13::convert_inplace` is a class member function. Is that correct? – NathanOliver Aug 10 '17 at 12:08
  • I did and it require still to create more custom functions, std::function and std::bind would be used for the member function and the other will be std::function, but I'm not really sure how to use them. What I want is that I shall write printtime only once. – Pella86 Aug 10 '17 at 12:09
  • Rot13::convert_inplace and Rot13::convert are both member functions. – Pella86 Aug 10 '17 at 12:10
  • You could give the functions a more similar interface by returning `std::string&` rather than `void` and simply return th input string for the *in-place* versions. – Galik Aug 10 '17 at 12:12
  • I wanted to see the difference between returning a new string or changing it in place, your solution is good but sounds a bit like a trick that obfuscate the scope of the functions? – Pella86 Aug 10 '17 at 12:12
  • Yes I misread. I edited the comment – Pella86 Aug 10 '17 at 12:14
  • A trick? Not really. I have a lot if string functions in my library that do exactly that. It has its own benefits in addition to making testing the functions easier to generalize. – Galik Aug 10 '17 at 12:17

3 Answers3

15

The easiest way, IMO, is to use a template instead of trying to write a function with a concrete type.

template<typename Function>
void printtime_inplace(string title, Function func)
{
    //...
    func(title);
    //...
}

This will now allow you to take anything that is a "function". You can pass it a regular function, a functor, a lambda, a std::function, basically, any callable. The compiler will stamp out different instantiations for you but as far as your code is concerned you are calling the same function.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • And I do the 3 different typedefs for my needs, `typedef string (*vfc)(string str)`, `typedef void (*vfc)(string str)` and `typedef void (*object::func)(string str)` ? – Pella86 Aug 10 '17 at 12:17
  • 1
    @Pella86 You could but this still will not work with member functions. With this single template function you don't need the typedefs. It will just work. – NathanOliver Aug 10 '17 at 12:19
  • I will have to anyhow write `(*object)->(*myfunc)()` in a separate function right? – Pella86 Aug 10 '17 at 12:21
  • @Pella86 I don't understand. Are you talking about if you pass a member function to `printtime_inplace`? – NathanOliver Aug 10 '17 at 12:23
  • In python you would write `def printtime_inplace(title, funct)` and will accept anything from functions to member functions... Now I wanted to have the same thing in C++. – Pella86 Aug 10 '17 at 12:32
  • 1
    @Pella86 Okay, this will do that. The only caveat is if you want to call a member function then you need to use something like `my_type my_object; printtime_inplace("some sting", [&](const auto& str){ my_object.my_function(str); });` – NathanOliver Aug 10 '17 at 12:36
  • Thank you, I will try it out, python surely make people lazy. – Pella86 Aug 10 '17 at 13:21
9

You can use std::function to provide such template:

#include <iostream>
#include <functional>
#include <string>
#include <type_traits>

void convert_inplace(std::string& mystr){}
std::string convert(const std::string& mystr){
    return mystr;
}
void bitrot_inplace(std::string& mystr){}

template<typename ret, typename par>
using fn = std::function<ret(par)>;

template<typename ret, typename par>
void caller(fn<ret,par> f) {
    typename std::remove_reference<par>::type p;
    ret r = f(p);
}

template<typename par>
void caller(fn<void,par> f) {
    typename std::remove_reference<par>::type p;
    f(p);
}

int main() {
    auto f1 = fn<void,std::string&>(convert_inplace);
    auto f2 = fn<std::string,const std::string&>(convert);
    auto f3 = fn<void,std::string&>(bitrot_inplace);
    caller(f1);
    caller(f2);
    caller(f3);
    return 0;
}

See the live demo.

user0042
  • 7,917
  • 3
  • 24
  • 39
  • Thank you for the answer, I think I'm still not sure what path I will take if the std::function or the template one, yet I like to have more options. – Pella86 Aug 10 '17 at 13:20
  • 3
    `std::function` has the advantage that you can call it with class members as well. – user0042 Aug 10 '17 at 13:23
  • @Pella86 Yes, or a bound lambda function. – user0042 Aug 10 '17 at 13:29
  • @user0042 I'm trying to write a template for functions without parameters unsuccessfully, I mean, I want this `fn(a_parameterless_function)` to be possible... do you know how to do that? – JPCF Dec 08 '19 at 10:34
0

If you want to have a function with variable arguments, here is an example that builds on user0042s answer (see also https://github.com/goblinhack/c-plus-plus-examples/blob/master/std_function_with_variadic_template/README.md)

#include <iostream>

int my_wrapped_function (int x, const std::string y)
{
    std::cout << "SUCCESS: Hello from my_wrapped_function(x=" << x << ", y=" << y << ");" << std::endl;
    return 43;
}

void my_argument_modifier (int &x)
{
    std::cout << "SUCCESS: Hello from my_argument_modifier(x=" << x << ") => " << x + 1 << ";" << std::endl;
    x++;
}

template<typename ret, typename T, typename... Rest>
using fn = std::function<ret(T, Rest...)>;

template<typename ret, typename T, typename... Rest>
ret wrapper(fn<ret, T, Rest...> f, T t, Rest... rest)
{
    return f(t, rest...);
}

template<typename ret, typename T, typename... Rest>
ret wrapper(fn<ret, T&, Rest&...> f, T& t, Rest&... rest)
{
    return f(t, rest...);
}

int main()
{
    // Wrap a function with variable arguments
    auto f1 = fn<int,int,const std::string>(my_wrapped_function);
    auto result = wrapper(f1, 42, std::string("hello"));
    // Result should be 43: 

    // Wrap a function that modifies its arguments
    auto f2 = fn<void,int&>(my_argument_modifier);
    wrapper(f2, result);
    // Result should be 44: 

    return 0;
}
Goblinhack
  • 2,859
  • 1
  • 26
  • 26