Generic Exponential Backoff Retry Mechanism C++11 - c++

I have written a generic exponential backoff retry loop in C++11. I'm using std::function to pass the callable to retry loop. callable will be retried if isRetriable function returns true.
#include <algorithm>
#include <cassert>
#include <chrono>
#include <functional>
#include <iostream>
#include <thread>
constexpr int64_t max_backoff_milliseconds = 30000; // 30 seconds
template <class R, class... Args>
R Retry(int max_retry_count, int64_t initial_dealy_milliseconds,
const std::function<bool(R)> &isRetriable,
const std::function<R(Args...)> &callable, Args &&... args)
{
int retry_count = 0;
while (true) {
auto status = callable(std::forward<Args>(args)...);
if (!IsRetriable(status)) {
return status;
}
if (retry_count >= max_retry_count) {
// Return status and abort retry
return status;
}
int64_t delay_milliseconds = 0;
if (initial_dealy_milliseconds > 0) {
delay_milliseconds =
std::min(initial_dealy_milliseconds << retry_count,
max_backoff_milliseconds);
}
std::cout << "Callable execution failed. Retry Count:"
<< retry_count + 1 << std::endl;
std::this_thread::sleep_for(
std::chrono::milliseconds(delay_milliseconds));
retry_count++;
}
}
bool isRetriable(int status) {
if (status == 5)
return true;
return false;
}
int foo(int x, int y) {
static int a = 1;
a += (x + y);
return a / 6;
}
int main() {
auto result = Retry(1000, 100, isRetriable, foo, 1, 3);
std::cout << result << std::endl;
return 0;
}
When I compile it, I'm getting below error:
prog.cpp: In function ‘int main()’:
prog.cpp:50:71: error: no matching function for call to ‘Retry(int,
int, bool (&)(int), int (&)(int, int), int, int)’
auto result = Retry<int, int, int>(1000, 100, isRetriable, foo, 1, 3);
^
prog.cpp:11:3: note: candidate: template<class R, class ... Args> R
Retry(int, int64_t, const std::function<bool(R)>&, const
std::function<_Res(_ArgTypes ...)>&, Args&& ...)
R Retry(int max_retry_count,
^~~~~
prog.cpp:11:3: note: template argument deduction/substitution failed:
prog.cpp:50:71: note: mismatched types ‘const
std::function<int(_ArgTypes ...)>’ and ‘int(int, int)’
auto result = Retry<int, int, int>(1000, 100, isRetriable, foo, 1, 3);
^
Could someone explain to me why I have this error?

I'm sure there's a good duplicate for this but...
Here's a shorter reproduction:
template <typename T> void foo(std::function<bool(T)> ) { }
bool maybe(int ) { return false; }
foo(maybe); // error: no matching function call to 'foo(bool (&)(int))'
You may ask - what?! maybe is something that's callable with some T that returns bool. But that's not how template deduction works. In order to deduce std::function<bool(T)> against an argument, that argument needs to be a std::function. maybe isn't a std::function, it's just a function, so that deduction fails. Any kind of deduction with a different kind of expression will also fail:
foo([](int ) { return true; }); // also error
Basically, trying to deduce a std::function is almost always the wrong thing to do. First, it's wrong because it won't work. Second, it's wrong because even if it did work, you're incurring type erasure in a context in which you probably don't need it.
What you want to do instead is deduce arbitrary callables, and then determine what these other arguments are based on those callables. The return type of callable is just what you get when you call callable with Args..., and you want to ensure that isRetriable is a predicate on that type.
One approach to that is:
template <typename Predicate, typename Callable, typename... Args,
// figure out what the callable returns
typename R = std::decay_t<std::invoke_result_t<Callable&, Args...>>,
// require that Predicate is actually a Predicate
std::enable_if_t<
std::is_convertible_v<std::invoke_result_t<Predicate&, R>, bool>,
int> = 0>
R Retry(int max_retry_count, int64_t initial_dealy_milliseconds,
Predicate&& isRetriable,
Callable&& callable,
Args&&... args)
{
// ....
}

Related

How to check a valid boost asio CompletionToken?

I'm writing Boost.Asio style async function that takes CompletionToken argument.
The argument can be function, function object, lambda expression, future, awaitable, etc.
CompletionToken is template parameter. If I don't restrict the argument, the function could match unexpected parameter such as int. So I want to write some restriction using std::enable_if. (My environment is C++17).
If CompletionToken takes parameters, then I can use std::is_invocable for checking.
See f1 in my example code.
However, I got a problem. If CompletionToken takes no parameters, then boost::asio::use_future makes an error.
See f2 in my example code.
After some of try and error, I got my solution. That is concatenating std::is_invocable and use_future_t checking by OR (||).
See f3 in my example code.
But it is not so elegant. In addition, I'm not sure other features supported by Boost.Asio e.g.) use_awaitable_t requires similar direct matching check.
I tried to find Boost.Asio provides type traits or predicate such as is_completion_token, but I couldn't find it.
Is there any better way to checking CompletionToken?
Godbolt link https://godbolt.org/z/sPeMo1GEK
Complete Code:
#include <type_traits>
#include <boost/asio.hpp>
// Callable T takes one argument
template <
typename T,
std::enable_if_t<std::is_invocable_v<T, int>>* = nullptr
>
void f1(T) {
}
// Callable T takes no argument
template <
typename T,
std::enable_if_t<std::is_invocable_v<T>>* = nullptr
>
void f2(T) {
}
template <template <typename...> typename, typename>
struct is_instance_of : std::false_type {};
template <template <typename...> typename T, typename U>
struct is_instance_of<T, T<U>> : std::true_type {};
// Callable T takes no argument
template <
typename T,
std::enable_if_t<
std::is_invocable_v<T> ||
is_instance_of<boost::asio::use_future_t, T>::value
>* = nullptr
>
void f3(T) {
}
int main() {
// no error
f1([](int){});
f1(boost::asio::use_future);
// same rule as f1 but use_future got compile error
f2([](){});
f2(boost::asio::use_future); // error
// a little complecated typechecking, then no error
f3([](){});
f3(boost::asio::use_future);
}
Outputs:
Output of x86-64 clang 13.0.1 (Compiler #1)
<source>:45:5: error: no matching function for call to 'f2'
f2(boost::asio::use_future); // error
^~
<source>:17:6: note: candidate template ignored: requirement 'std::is_invocable_v<boost::asio::use_future_t<std::allocator<void>>>' was not satisfied [with T = boost::asio::use_future_t<>]
void f2(T) {
^
1 error generated.
If you have c++20 concepts, look below. Otherwise, read on.
When you want to correctly implement the async-result protocol using Asio, you would use the async_result trait, or the async_initiate as documented here.
This should be a reliable key for SFINAE. The template arguments to async_result include the token and the completion signature(s):
Live On Compiler Explorer
#include <boost/asio.hpp>
#include <iostream>
using boost::asio::async_result;
template <typename Token,
typename R = typename async_result<std::decay_t<Token>, void(int)>::return_type>
void f1(Token&&) {
std::cout << __PRETTY_FUNCTION__ << "\n";
}
template <typename Token,
typename R = typename async_result<std::decay_t<Token>, void()>::return_type>
void f2(Token&&) {
std::cout << __PRETTY_FUNCTION__ << "\n";
}
int main() {
auto cb1 = [](int) {};
f1(cb1);
f1(boost::asio::use_future);
f1(boost::asio::use_awaitable);
f1(boost::asio::detached);
f1(boost::asio::as_tuple(boost::asio::use_awaitable));
auto cb2 = []() {};
f2(cb2);
f2(boost::asio::use_future);
f2(boost::asio::use_awaitable);
f2(boost::asio::detached);
f2(boost::asio::as_tuple(boost::asio::use_awaitable));
}
Already prints
void f1(Token&&) [with Token = main()::<lambda(int)>&; R = void]
void f1(Token&&) [with Token = const boost::asio::use_future_t<>&; R = std::future<int>]
void f1(Token&&) [with Token = const boost::asio::use_awaitable_t<>&; R = boost::asio::awaitable<int, boost::asio::any_io_executor>]
void f1(Token&&) [with Token = const boost::asio::detached_t&; R = void]
void f1(Token&&) [with Token = boost::asio::as_tuple_t<boost::asio::use_awaitable_t<> >; R = boost::asio::awaitable<std::tuple<int>, boost::asio::any_io_executor>]
void f2(Token&&) [with Token = main()::<lambda()>&; R = void]
void f2(Token&&) [with Token = const boost::asio::use_future_t<>&; R = std::future<void>]
void f2(Token&&) [with Token = const boost::asio::use_awaitable_t<>&; R = boost::asio::awaitable<void, boost::asio::any_io_executor>]
void f2(Token&&) [with Token = const boost::asio::detached_t&; R = void]
void f2(Token&&) [with Token = boost::asio::as_tuple_t<boost::asio::use_awaitable_t<> >; R = boost::asio::awaitable<std::tuple<>, boost::asio::any_io_executor>]
C++20 Concepts
Now keep in mind the above is "too lax" due to partial template instantiation. Some parts of async_result aren't actually used. That means that f2(cb1); will actually compile.
The linked docs even include the C++20 completion_token_for<Sig> concept that allows you to be precise at no effort: Live On Compiler Explorer
template <boost::asio::completion_token_for<void(int)> Token> void f1(Token&&) {
std::cout << __PRETTY_FUNCTION__ << "\n";
}
template <boost::asio::completion_token_for<void()> Token> void f2(Token&&) {
std::cout << __PRETTY_FUNCTION__ << "\n";
}
Otherwise
In practice you would always follow the Asio recipe, and that guarantees that all parts are used. Apart from the example in the documentation, you can search existing answers
Example:
template <typename Token>
typename asio::async_result<std::decay_t<Token>, void(error_code, int)>::return_type
async_f1(Token&& token) {
auto init = [](auto completion) {
auto timer =
std::make_shared<asio::steady_timer>(boost::asio::system_executor{}, 1s);
std::thread(
[timer](auto completion) {
error_code ec;
timer->wait(ec);
std::move(completion)(ec, 42);
},
std::move(completion))
.detach();
};
return asio::async_result<std::decay_t<Token>, void(error_code, int)>::initiate(
init, std::forward<Token>(token));
}

getting type index from a type list in C++

When I try and get the index of a type within a list of types using below, the code compiles and returns the correct value when the else clause is used. However when I skip the else clause and place the return getIndex<T, Ts...>(x + 1); just after the end of the if clause the code fails to compile as it continues to unwind getIndex<T, Ts...> recursively resulting in the error shown below. This is the case with gcc and clang. Is this expected?
#include <type_traits>
template <typename T, typename U, typename ...Ts>
constexpr int getIndex(int x = 0)
{
if constexpr(std::is_same_v<T,U>)
{
return x;
}
else
{
return getIndex<T, Ts...>(x + 1);
}
}
int main()
{
return getIndex<int, float, double, int, char>();
}
Error when moving return outside of else
getI.cc: In instantiation of ‘int getIndex(int) [with T = int; U = char; Ts = {}]’:
getI.cc:9:32: recursively required from ‘int getIndex(int) [with T = int; U = double; Ts = {int, char}]’
getI.cc:9:32: required from ‘int getIndex(int) [with T = int; U = float; Ts = {double, int, char}]’
getI.cc:14:51: required from here
getI.cc:9:32: error: no matching function for call to ‘getIndex<int>(int)’
9 | return getIndex<T, Ts...>(x + 1);
| ~~~~~~~~~~~~~~~~~~^~~~~~~
getI.cc:3:5: note: candidate: ‘template<class T, class U, class ... Ts> int getIndex(int)’
3 | int getIndex(int x = 0)
| ^~~~~~~~
getI.cc:3:5: note: template argument deduction/substitution failed:
getI.cc:9:32: note: couldn’t deduce template parameter ‘U’
9 | return getIndex<T, Ts...>(x + 1);
| ~~~~~~~~~~~~~~~~~~^~~~~~~
Peter Taran's answer has already touched on why the code compiles with the else clause, but he didn't really touch on why the compilation error occurs or how to fix it. Here is a working version of your code:
#include <type_traits>
template <typename T>
constexpr int getIndex(int x = 0)
{
return -1;
}
template <typename T, typename U, typename ...Ts>
constexpr int getIndex(int x = 0)
{
if constexpr (std::is_same_v<T,U>)
{
return x + 1;// NOTE: you have a bug on this line
}
else
{
return getIndex<T, Ts...>(x + 1);
}
}
int main()
{
constexpr int index = getIndex<int, float, double, int, char>();
return 0;
}
Admittedly, I haven't used variadic templates much, but from what I am understanding here, the main problem is in this snippet of your compilation errors:
getI.cc:9:32: error: no matching function for call to ‘getIndex<int>(int)’
Despite having a base case defined in your recursive template, it seems like variadic templates are required to fully unpack. Because of that, you need a getIndex<T> defined, even if the code is not reached in your example. Adding the single argument getIndex<T> allowed the code to compile and run.
EDIT: This post is also related, a good read, and is also another possible solution to your issue.
As I realized, broken code is:
template <typename T, typename U, typename ...Ts>
constexpr int getIndex(int x = 0)
{
if constexpr(std::is_same_v<T,U>) {
return x;
}
return getIndex<T, Ts...>(x + 1);
}
Accoring to this article:
In a constexpr if statement, the value of condition must be an
expression contextually converted to bool, where the conversion is a
constant expression. If the value is true, then statement-false is
discarded (if present), otherwise, statement-true is discarded.
Note that nothing is said about discarding statements outside if-else clause, so you should expect that recusive getIndex's will be instantiated.

Why is std::forward turning my lvalue into an rvalue?

template<class T>
struct IntHolder {
T i;
};
template<class T>
void addOne(T& t) {
t.i += 1;
}
template<class... Args>
void addAll(Args... args) {
// Magic to run addOne on each arg
int dummy[] = { 0, ((void)addOne(std::forward<Args>(args)), 0)... };
}
int main() {
IntHolder<int> x{2};
IntHolder<double> t{3};
addAll(x, t);
return 0;
}
This toy example won't compile because
prog.cc: In instantiation of 'void addAll(Args ...) [with Args = {IntHolder<int>, IntHolder<double>}]':
prog.cc:60:16: required from here
prog.cc:54:39: error: invalid initialization of non-const reference of type 'IntHolder<int>&' from an rvalue of type 'IntHolder<int>'
int dummy[] = { 0, ((void)addOne(std::forward<Args>(args)), 0)... };
^
prog.cc:48:6: note: initializing argument 1 of 'void addOne(T&) [with T = IntHolder<int>]'
void addOne(T& t) {
What I thought would happen here is that addAll gets two lvalues passed in, and then addOne would be called on each of those as an lvalue. However, it seems somewhere along the way, the compiler thinks that the argument is getting converted to an rvalue. Why might this be?
You're declaring parameter as non-reference, i.e. pass-by-value, then when being passed x and t, the type would be deduced as IntHolder<int> and IntHolder<double>. Then std::forward<Args> would convert them into rvalues.
For forwarding reference it should be
template<class... Args>
void addAll(Args&&... args) {
// ^^
// Args would be deduced as IntHolder<int>&, IntHolder<double>&
// then std::forward<Args>(args) yields lvalues
int dummy[] = { 0, ((void)addOne(std::forward<Args>(args)), 0)... };
}

Using std::bind in template function parameter

This has probably been asked in some way before, but I cannot find the right search keywords.
While writing testing functions, I decided to refactor the testing code into a template function:
#include <iostream>
#include <functional>
#include <utility>
#include <vector>
template <typename In, typename Exp >
void runTest (
std::pair<In, Exp> testParams,
Exp (*testFunction)(In)
/*std::function< Exp(In)> testFunction */ )
{
Exp result = testFunction(testParams.first);
std::cout << "Result : " << (result == testParams.second? "SUCCESS":"FAILURE")
<< " expected : " << testParams.second
<< " got : " << result
<< std::endl;
}
Fill a vector with input and expected results and pass the pairs along with the function we want to test. Worked great for one function:
long f1 (long a1)
{
return a1 + 100;
}
void testf1()
{
std::vector<std::pair<long, long> > testCases = {
{100,200},
{300,400}
};
for (auto test : testCases) {
runTest (test, f1);
}
}
but then had to test one that takes two parameters. "Ok, no problem, I'll std::bind1st... oh, that's deprecated... std::bind should to it though, right? the first argument and pass that along to runTest"...
long f2 (long a1, long a2)
{
return a1+a2;
}
void testf2()
{
long a1 = 1234;
std::vector<std::pair<long, long> > testCases = {
{0,1234},
{2,1238},
{11,1245}
};
for (auto test : testCases){
auto f2bound = std::bind(f2, a1, std::placeholders::_2);
runTest (test, f2bound);
}
}
But the compiler says 'no' :
~/src/cpplay/onestens$ g++ -m64 --std=c++11 -o soq.out soQuestionBind.cpp -g
soQuestionBind.cpp: In function ‘void testf2()’:
soQuestionBind.cpp:50:31: error: no matching function for call to ‘runTest(std::pair<long int, long int>&, std::_Bind<long int (*(long int, std::_Placeholder<2>))(long int, long int)>&)’
runTest (test, f2bound);
^
soQuestionBind.cpp:7:6: note: candidate: template<class In, class Exp> void runTest(std::pair<_T1, _T2>, Exp (*)(In))
void runTest (
^
soQuestionBind.cpp:7:6: note: template argument deduction/substitution failed:
soQuestionBind.cpp:50:31: note: mismatched types ‘Exp (*)(In)’ and ‘std::_Bind<long int (*(long int, std::_Placeholder<2>))(long int, long int)>’
runTest (test, f2bound);
^
I'm a bit behind C++11 (and 14 and 17) but this should be possible, right?
I guess the object returned by std::bind couldn't be coerced into a simple function pointer... so how must my template parameter be defined to accept the bound function?
I guess the object returned by std::bind couldn't be coerced into a simple function pointer.
Correct.
The typical way of getting generic callables is to use a simple template parameter. No need to use function pointers, std::function or whatever:
template <typename T, typename U, typename F>
void runTest (
std::pair<T, U> testParams,
F testFunction )
{
auto result = testFunction(testParams.first);
// do something ...
}
Now you can use std::bind, but I'd recommend to use a lambda instead.

C++ Lambdas and Variadic Templated Wrappers

I am trying to execute the following code in C++. The program converts a lambda with no capture to a function pointer.
#include <utility>
template <typename R, typename... Args>
R run(R (*func)(Args...), Args&&... args) {
func(std::forward<Args>(args)...);
}
int main() {
run([] (int x, int y) {
return x + y;
}, 100, 200);
return 0;
}
However, when I compile it, I get the following error -
test.cc: In function ‘int main()’:
test.cc:11:20: error: no matching function for call to ‘run(main()::<lambda(int, int)>, int, int)’
}, 100, 200);
^
test.cc:11:20: note: candidate is:
test.cc:4:3: note: template<class R, class ... Args> R run(R (*)(Args ...), Args&& ...)
R run(R (*func)(Args...), Args&&... args) {
^
test.cc:4:3: note: template argument deduction/substitution failed:
test.cc:11:20: note: mismatched types ‘R (*)(Args ...)’ and ‘main()::<lambda(int, int)>’
}, 100, 200);
^
As far as I am aware this is fine. I have also tried explicitly giving the template arguments in the call to run. That doesnt work either.
Any ideas?
A lambda is not a function pointer. It cannot be deduced as a function pointer. It is a closure. However, if (and only if) it takes no capture, it can be explicitly converted to a function pointer via some sorcery:
run(+[] (int x, int y) {
// ^^^
return x + y;
}, 100, 200);
That said, it'd be better to simply have run take an arbitrary callable:
template <typename F, typename... Args>
auto run(F func, Args&&... args)
-> decltype(func(std::forward<Args>(args)...)) // only for C++11
{
return func(std::forward<Args>(args)...);
}