3

I have a macro that implements a retry mechanism that looks like that:

#define RETRY(function_name, param_list, max_attempts, retry_interval_usecs, error_var) \
    do {                                                                                                   \
        int _attempt_;                                                                                     \        
                                                                                               \
        for (_attempt_ = 0; _attempt_ < max_attempts; _attempt_++)                                         \
        {                                                                                                  \
            error_var = function_name param_list;                                                          \
            if (error_var == SUCCESS)                                                         \
            {                                                                                              \
                break;                                                                                     \
            }                                                                                              \
                                                                                                           \
            usleep(retry_interval_usecs);                                                                  \
        }                                                                                                                                                                                                                                                                                                   \
    } while (0)

This is functional, but I keep hearing that within a C++ application, defines are not favorable.

Now I looked into a retry function that takes a function pointer as an argument. But I seem to have missed something since I can't get this code to compile.

Note: This code below is NON-Functional, I thought I can post a simple code to illustrate what I want to do:

void retry(int (*pt2Func)(void* args))
{
    const int numOfRetries = 3;
    int i = 1;
    do
    {
        //Invoke the function that was passed as argument
        if((*pt2Func)(args)) //COMPILER: 'args' was not declared in this scope
        {
          //Invocation is successful
          cout << "\t try number#" << i <<" Successful \n";
          break;
        }

        //Invocation is Not successful
        cout << "\t try number#" << i <<" Not Successful \n";
        ++i;

        if (i == 4)
        {
          cout<< "\t failed invocation!";
        }

    }while (i <= numOfRetries);
}

int Permit(int i)
{
    //Permit succeeds the second retry
    static int x = 0;
    x++;
    if (x == 2 && i ==1 ) return 1;
    else return 0;
}

int main()
{
    int i = 1;
    int * args = &i;


    retry(&Permit(args));
}

So Basically my question is:

  • How can I pass a general function with different parameter (in type and number) to the retry method? without encapsulating the functions within a class?

Is that doable?

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Kam
  • 5,878
  • 10
  • 53
  • 97
  • This is trickier than you think, because `Permit(args)` executes it then and there, and then you're trying to pass the address of the resulting `int` to the `retry` function. What you need is `std::bind`. – Mooing Duck Jan 16 '13 at 21:34
  • In C++03, I believe you're stuck with "encapsulating the function" and its arguments in a class type in order to call it with no arguments. – aschepler Jan 16 '13 at 21:36
  • @Mooing Duck, I am looking into boost::bind, but I can't figure out how this might help :$. If I bind the function with its parameters how can I pass that to retry(..) do you have an example? – Kam Jan 16 '13 at 21:46

5 Answers5

4

All existing answers are C++11, so here's a minor modification to your code to make it work using boost (which is C++03)

//takes any function or function like object
//expected function takes no parameters and returns a bool
template<class function_type>
void retry(function_type function, int numOfRetries = 3)
{
    int i = 1;
    do
    {
        //Invoke the function that was passed as argument
        if(function())
            blah blah blah

and in main

int main()
{
    int i = 1;
    //bind takes a function and some parameters
    //and returns a function-like object with a different parameter set
    //in this case, the function Permit, and the first thing it gets passed is i
    //this means the resulting function-like object doesn't need any parameters
    //return type is the same as the original function
    retry(boost::bind(Permit, i));
}

Proof of C++03 compilation and execution

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • I was about to suggest to you to have an answer so I accept. Thank you! the post that you made here works: http://ideone.com/tZN87N – Kam Jan 16 '13 at 21:53
3

The following solution uses C++11 features - the addition that it is not possible to use C++11 was done after the development of the solution started.

One C++ way is using std::function.

The following code gives examples for function, 'callable' classes and lambda expressions.

#include <string>
#include <iostream>
#include <functional>
#include <unistd.h>

// Minimalistic retry 
bool retry( std::function<bool()> func, size_t max_attempts, 
    unsigned long retry_interval_usecs ) {
  for( size_t attempt { 0 }; attempt < max_attempts; ++attempt ) {
    if( func() ) { return true; }
    usleep( retry_interval_usecs );
  }
  return false;
}

// Ex1: function
int f(std::string const u) {
  std::cout << "f()" << std::endl;
  return false;
}

// Ex2: 'callable' class
struct A {

  bool operator() (std::string const & u, int z) {
    ++m_cnt;
    std::cout << "A::op() " << u << ", " << z << std::endl;

    if( m_cnt > 3 ) {
      return true;
    }
    return false;
  }

  int m_cnt { 0 };
};

int main() {

  A a;

  bool const r1 = retry( std::bind(f, "stringparam1"), 3, 100 );
  bool const r2 = retry( std::bind(a, "stringparam2", 77), 5, 300 );
  // Ex 3: lambda
  bool const r3 = retry( []() -> bool
    { std::cout << "lambda()" << std::endl; return false; }, 5, 1000 );

  std::cout << "Results: " << r1 << ", " << r2 << ", " << r3 << std::endl;

  return 0;
}

Tested this with gcc 4.7.2. Output:

f()
f()
f()
A::op() stringparam2, 77
A::op() stringparam2, 77
A::op() stringparam2, 77
A::op() stringparam2, 77
lambda()
lambda()
lambda()
lambda()
lambda()
Results: 0, 1, 0
Andreas Florath
  • 4,418
  • 22
  • 32
1

There are two ways.

Using a variadic template function:

// All in header file:
template <typename F, typename... Args>
void retry1(F func, Args&& ... args) {
    //...
    if (func(std::forward<Args>(args)...))
    ; //...
}

// Call like:
retry1(Permit, i);

Or using a std::function and a lambda:

// In header file
void retry2(std::function<bool()> func);

// In a source file
void retry2(std::function<bool()> func) {
    //...
    if (func())
    ; //...
}

// Call like:
retry2([]() -> bool { return Permit(i); });
aschepler
  • 70,891
  • 9
  • 107
  • 161
1

FWIW, i'm trying to fix your initial exmaple. (There may be other drawbacks, and no one will go this way, since there are better solutions)

Your initial definition for retry can be written as:

void retry(int (*pt2Func)(void* args), void* args)

It gets a function pointer (to a function returning and int and a void* argument) and an additional (void*) argument.

The Permit function is now:

int Permit(void* pvi)

The main function now calls the retry/Permit as follows:

    retry(&Permit, static_cast<void*>(args));

Complete example

#include <iostream>
using std::cout;

void retry(int (*pt2Func)(void* args), void* args)
{
    const int numOfRetries = 3;
    int i = 1;
    do
    {
        //Invoke the function that was passed as argument
        if((*pt2Func)(args)) //not changed: args is now declared
        {
          //Invocation is successful
          cout << "\t try number#" << i <<" Successful \n";
          break;
        }

        //Invocation is Not successful
        cout << "\t try number#" << i <<" Not Successful \n";
        ++i;

        if (i == 4)
        {
          cout<< "\t failed invocation!";
        }

    }while (i <= numOfRetries);
}

int Permit(void* pvi)
{
    //Permit succeeds the second retry
    int i = *(static_cast<int*>(pvi));
    static int x = 0;
    x++;
    if (x == 2 && i ==1 ) return 1;
    else return 0;
}

int main()
{
    int i = 1;
    int * args = &i;

    retry(&Permit, static_cast<void*>(args));
}
huch
  • 675
  • 8
  • 13
0

Well, if you're using C++11, you have Lambda Expressions.

There's also this question: Write a function that accepts a lambda expression as argument that provides another relevant example.

EDIT after seeing you can't use C++11

In that case, I'd just keep the macro and forget about it. While some people might frown upon it, you have a good reason to use it - it's easier and makes more sense than another solution. Just write that in a comment above the macro, so that 5 years from now when people try to figure out why you decided not to use std:forward or lambda expressions, they'll know.

Community
  • 1
  • 1
zmbq
  • 38,013
  • 14
  • 101
  • 171