Skip to content

Instantly share code, notes, and snippets.

@Redchards
Last active December 5, 2023 13:54
Show Gist options
  • Save Redchards/c5be14c2998f1ca1d757 to your computer and use it in GitHub Desktop.
Save Redchards/c5be14c2998f1ca1d757 to your computer and use it in GitHub Desktop.
Simple implementation of a function like std::bind.
#include <iostream>
#include <functional>
#include <tuple>
#include <type_traits>
// Actually, a subtle bug may arise when the function is using index_constant as parameter.
// For this reason, a more correct implementation should define a specialized class, hidden in a namespace,
// and forbid the use in other places.
// An easier, more correct, but more verbose way would be to just define two different classes for the argument list
// and the bounded arguments, to avoid the confusion with the index_constant bracket operator overload.
// However, as this is only an academic example, it should strive to stay extremly simple and straightforward, so this
// improvment will not be applied. I just wanted anyone wanting to use this somewhere to be aware of this caveheat.
// Moreover, you should use std::bind anyway.
template<size_t n>
using index_constant = std::integral_constant<size_t, n>;
template<class ... Args>
class binder_list
{
public:
template<class ... TArgs>
constexpr binder_list(TArgs&&... args) noexcept
: boundedArgs_{std::forward<TArgs>(args)...}
{}
template<size_t n>
constexpr decltype(auto) operator[](index_constant<n>) noexcept
{
return std::get<n>(boundedArgs_);
}
private:
std::tuple<Args...> boundedArgs_;
};
template<class ... Args>
class callee_list
{
public:
template<class ... TArgs>
constexpr callee_list(TArgs&&... args) noexcept
: boundedArgs_{std::forward<TArgs>(args)...}
{}
template<class T, std::enable_if_t<(std::is_placeholder<std::remove_reference_t<T>>::value == 0)>* = nullptr>
constexpr decltype(auto) operator[](T&& t) noexcept
{
return std::forward<T>(t);
}
template<class T, std::enable_if_t<(std::is_placeholder<T>::value != 0)>* = nullptr>
constexpr decltype(auto) operator[](T) noexcept
{
return std::get<std::is_placeholder<T>::value - 1>(std::move(boundedArgs_));
}
private:
std::tuple<Args&&...> boundedArgs_;
};
template<class Fn, class ... Args>
class binder
{
public:
template<class TFn, class ... TArgs>
constexpr binder(TFn&& f, TArgs&&... args) noexcept
: f_{std::forward<TFn>(f)},
argumentList_{std::forward<TArgs>(args)...}
{}
// Please C++, give me a way of detecting noexcept :'(
template<class ... CallArgs>
constexpr decltype(auto) operator()(CallArgs&&... args)
//noexcept(noexcept(call(std::make_index_sequence<sizeof...(Args)>{}, std::declval<Args>()...)))
{
return call(std::make_index_sequence<sizeof...(Args)>{}, std::forward<CallArgs>(args)...);
}
private:
template<class ... CallArgs, size_t ... Seq>
constexpr decltype(auto) call(std::index_sequence<Seq...>, CallArgs&&... args)
//noexcept(noexcept(f_(this->binder_list<CallArgs...>{std::declval<CallArgs>()...}[this->argumentList_[index_constant<Seq>{}]]...)))
{
return f_((callee_list<CallArgs...>{std::forward<CallArgs>(args)...}[argumentList_[index_constant<Seq>{}]])...);
}
private:
std::function<std::remove_reference_t<std::remove_pointer_t<Fn>>> f_;
binder_list<Args...> argumentList_;
};
namespace me
{
template<class Fn, class ... Args>
binder<Fn, Args...> bind(Fn&& f, Args&&... args)
{
return binder<Fn, Args...>{std::forward<Fn>(f), std::forward<Args>(args)...};
}
}
@MasterAler
Copy link

Thanks a lot for this sample!
Helped me once with my problem, much better than reading standard or boost sources.

By the way. AFAIK, std::bind stores copies of the given parameters. Something like std::tuple<typename std::decay_t<Args>...> boundedArgs_; could improve it.

@Redchards
Copy link
Author

Hm, pretty sure it's non-conforming in relation to references (i.e. args like int&).

Thanks for pointing this out! This example is indeed non-conforming, the standard mandating that the bound arguments be copied and the cv qualifier only added downstream during the effective function call. It wouldn't make sense to do it as I've done above and had I used it in actual code I would've realized as much as you could eventually want to bind a value that has a cv & without it still being in the scope while calling the function. At the time, I was unaware of std::ref and std::cref, hence why I wrote it this way, thinking "well, how could I pass by reference then?". Will update it soon with correct behavior. Thanks again.

@Redchards
Copy link
Author

Thanks a lot for this sample!
Helped me once with my problem, much better than reading standard or boost sources.

By the way. AFAIK, std::bind stores copies of the given parameters. Something like std::tuple<typename std::decay_t<Args>...> boundedArgs_; could improve it.

Same response as above with the little added bonus that yeah, the "binder_list" class is useless, no flipping idea why I wrote it this way. Gonna update it with proper standard compliance and taking your remark into account. Thanks.

Copy link

ghost commented Jul 20, 2020

you can detect noexcept, like this:

noexcept(run(args...))
it returns a bool, plug that back into noexcept

noexcept(noexcept(run(args...)))

@Redchards
Copy link
Author

you can detect noexcept, like this:

noexcept(run(args...))
it returns a bool, plug that back into noexcept

noexcept(noexcept(run(args...)))

Yes, as you can see in the code comments, I have indeed tried to do that but it must've been acting out back then. Bear in mind that I have written this code a few years ago.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment