Search code examples
c++variadic-templatesstdthreadperfect-forwarding

Forward types in variadic template as values/references according to function signature


This question relates to this, this and potentially this.

I have the following class, in which the AddFunction method receives a function and a list of arguments to that function, then spawns an std::thread calling the passed function with the passed arguments:

class Processes {
public:
  Processes() {}

  ~Processes() {
    for (auto &t : threads_) {
      t.join();
    }
  }

  template <class Function, typename... Args>
  void AddFunction(Function &&func, Args &&... args) {
    threads_.emplace_back(std::forward<Function>(func),
                          std::forward<Args>(args)...);
  }

private:
  std::vector<std::thread> threads_;
}

This results in a copy for every argument, and compilation will fail if an object is not copyable, because std::thread requires references to be wrapped in std::ref in order to guarantee that this object will exceed the thread's lifetime, and will copy it otherwise.

I want to pass objects by reference when specified in the target function signature.

I tried using a lambda:

template <class Function, typename... Args>
void AddFunction(Function &&func, Args &&... args) {
  threads_.emplace_back([&]() { func(std::forward<Args>(args)...); });
}

But this results in incorrect behavior, as the lambda captures values by reference before passing them by value, resulting in capture by reference behavior.

How do I implement a function that forwards arguments either as value or reference according to the target function signature?


Example:

void Foo(int a, std::vector<int> const &b) { /* ... */ }

int main() {
  Processes procs;
  int a = 6;
  std::vector<int> b;
  procs.AddFunction(
    Foo,
    a, // Should be passed by value
    b  // Should be passed by reference (as implemented by std::ref)
  );
  return 0;
}

Solution

  • You might change function signature to be less generic:

    First some helpers:

    template <typename T> struct non_deducible { using type = T; };
    template <typename T> using non_deducible_t = typename non_deducible<T>::type;
    
    template <typename T>
    auto passed_by(T& t, std::true_type)
    {
        return std::ref(t);
    }
    
    template <typename T>
    T&& passed_by(T&& t, std::false_type)
    {
        return std::forward<T>(t);
    }
    

    And then

    template <class Ret, typename... Args>
    void AddFunction(Ret (*func)(Args...), non_deducible_t<Args>... args) {
        threads_.emplace_back(func,
                              passed_by(std::forward<Args>(args),
                                        std::is_reference<Args>{})...);
    }