Benjamin Kloster
Benjamin Kloster

Reputation: 372

Binding function arguments in C++11

I'd like to generically "pickle" function calls so they can be executed later. The return type of those functions will always be void (for now). Something like this:

template<typename F, typename... Args>
std::function<void()> 
pickle(F function, Args&&... args) {
    return std::bind(F, args...);
}

The problem is, if args contains a const reference, std::bind tries to copy construct the value, which is not always desired or even valid if the type lacks a copy constructor. How do I forward the arguments in a way that uses std::ref for lvalue references and the normal std::forward for lvalue references?

Example

#include <functional>

class NonCopyable {
public:

    NonCopyable() {}

    NonCopyable(const NonCopyable&) = delete;
};

template<typename F, typename... Args>
std::function<void()>
pickle(F function, Args&&... args)
{
    return std::bind(function, std::forward<Args>(args)...);
}

int main()
{
    NonCopyable obj;
    auto f = pickle(
        [](const NonCopyable&) {},
        obj
    );
    return 0;
}

The above snippet won't compile, complaining about the deleted copy constructor. (I used forward here because someone suggested it, but has since deleted their answer, it seems).

Upvotes: 7

Views: 1852

Answers (2)

Xeo
Xeo

Reputation: 131887

Overloading, yay.

// also does the correct thing for `T const`
template<class T>
std::reference_wrapper<T> maybe_ref(T& v, int){ return std::ref(v); }

// just forward rvalues along
template<class T>
T&& maybe_ref(T&& v, long){ return std::forward<T>(v); }

template<typename F, typename... Args>
std::function<void()> 
pickle(F function, Args&&... args) {
    return std::bind(function, maybe_ref(std::forward<Args>(args), 0)...);
}

The int/long parameters and 0 argument disambiguate the lvalue case for compilers that find the overloads to be ambiguous, and doesn't do any harm otherwise.

Upvotes: 8

ecatmur
ecatmur

Reputation: 157484

This is a bit ugly (overuse of enable_if), but it works:

template<typename T> typename std::enable_if<
  !std::is_lvalue_reference<T>::value, T &&>::type
  forward_as_ref(typename std::remove_reference<T>::type &t) {
    return static_cast<T &&>(t);
  }
template<typename T> typename std::enable_if<
  !std::is_lvalue_reference<T>::value, T &&>::type
  forward_as_ref(typename std::remove_reference<T>::type &&t) {
    return t;
  }
template<typename T> typename std::enable_if<
  std::is_lvalue_reference<T>::value,
  std::reference_wrapper<typename std::remove_reference<T>::type>>::type
  forward_as_ref(T t) {
    return t;
  }

Here's a version using class template specialization instead:

template<typename T> struct forward_as_ref_type {
   typedef T &&type;
};
template<typename T> struct forward_as_ref_type<T &> {
   typedef std::reference_wrapper<T> type;
};

template<typename T> typename forward_as_ref_type<T>::type forward_as_ref(
   typename std::remove_reference<T>::type &t) {
      return static_cast<typename forward_as_ref_type<T>::type>(t);
   }
template<typename T> T &&forward_as_ref(
   typename std::remove_reference<T>::type &&t) {
      return t;
   }

Upvotes: 0

Related Questions