2

Basically I just want to wrap any callable object and their arguments in a Task obj that can be called later. Here are the code that come to mind:

Lets say all those callable type have a member type can be seemed as if they are defined like this:

template<typename TReturn, typename...TArgs>
struct SomeFunction{
    using ArgTypes = TArgs; // won't compile of course
}

And the Task Template can be defined like this:

template<typename TFunction>
class Task {
public:
    Task(TFunction fun, typename TFunction::ArgTypes...args) // won't compile
        : fun_(fun), args_(args){}
    void operator()()
    {
        fun_(args_...); // won't compile: workaround 1
    }
private: 
    typename TFunction::ArgTypes... args_; // won't compile: workaround 2
    TFunction fun_;
};

The problem lies in the definition of Task's constructor. Is there any way to achieve it? Of course I can define it as a template constructor:

template<typename...TArgs>
Task(TFunction fun, TArgs...args)

But in this way the compiler won't know that the TArgs are the same as TFunction::ArgTypes. Consequently, the error messages are ridiculous when wrong arguments passed to it.

workaround 1 : C++ How to store a parameter pack as a variable

workaround 2 : Is it possible to "store" a template parameter pack without expanding it?

  • Use a tuple to store the arguments? – Kerrek SB Jul 17 '14 at 11:23
  • You could use a `tuple`. – leemes Jul 17 '14 at 11:23
  • No, my problem is not the tuple one. that's [workaround][2] – user3787628 Jul 17 '14 at 11:24
  • `Consequently, the error messages are ridiculous when wrong arguments passed to it.` What do you mean? – leemes Jul 17 '14 at 11:28
  • @leemes I want the error messages to be mismatching arguments, but in reality its type cast between tuple error. – user3787628 Jul 17 '14 at 11:31
  • What is the problem with `std::bind` and `std::function` – David Rodríguez - dribeas Jul 17 '14 at 11:32
  • @DavidRodríguez-dribeas std::function can't be tested for equality. – user3787628 Jul 17 '14 at 11:40
  • You want to be able to test that the function *and* the arguments are the same? May I ask why? – David Rodríguez - dribeas Jul 17 '14 at 12:22
  • @DavidRodríguez-dribeas He partly answered that in a comment to my answer: "Because they need to be added and/or removed from some container." But I'm not sure if and if yes then how he puts tasks of different signatures in a container. To me it sounds like he writes some deferred task execution mechanism, which can execute any task (i.e. has a single queue of tasks), so he needs to compare tasks of **different** signatures against each other (yet I don't know which container type he uses which requires `operator==`). But this requires type erasure of some kind, i.e. something like my answer. – leemes Jul 17 '14 at 12:40

2 Answers2

2

You could use a std::tuple<TArgs...> to store the arguments and unpack them in the call-operator. If you want to define TArgs in some way in a function type, you should define them as a tuple there:

template<typename TReturn, typename...TArgs>
struct SomeFunction{
    using ArgTypesTuple = std::tuple<TArgs...>;
    //                    ^^^^^^^^^^
}

But nevertheless, I think it's not worth the effort to save the arguments in your Task object with a lot of boilerplate code... The call operator would look somewhat ugly to reconstruct / unpack the arguments from the tuple to a list of arguments.

The much easier solution is to make a lambda which captures the arguments at construction time of your Task object, which doesn't even need to be a template anymore:

class Task {
public:
    template<typename TFunction, typename ...ArgTypes>
    Task(TFunction fun, ArgTypes... args)
        : fun_([=]{ fun(args...); }) {}
    void operator()()
    {
        fun_();
    }
private: 
    std::function<void()> fun_;
};
leemes
  • 44,967
  • 21
  • 135
  • 183
  • Another option would be to use `std::bind`, rather than the lambda – Dave S Jul 17 '14 at 11:30
  • No, not std::function. It can't be tested for equality. – user3787628 Jul 17 '14 at 11:34
  • Why do you need to check for equality? And how do you define equality? Function pointer **and** arguments? Do you want to store the tasks in a container which needs the equality operator? – leemes Jul 17 '14 at 11:35
  • @leemes Because they need to be added and/or removed from some container. – user3787628 Jul 17 '14 at 11:37
  • Which container type? You could simply add an `id` to your tasks and use that for defining the equality. The `id`s can be assigned using a static counter, and the mechanism can be inside `Task` and hidden completely. – leemes Jul 17 '14 at 11:39
  • @user3787628 And if you keep `Task` as being a template, you will have difficulties storing it in a container... My version of `Task` isn't a template and uses `std::function`'s type erasure to hold the captured arguments automatically. Also, why not simply keeping it in a simple `std::vector` or `std::queue`? It sounds like you want to store a queue of tasks somewhere, which you want to execute some time, so that makes perfect sense to me to use `std::queue`. No equality operator needed. – leemes Jul 17 '14 at 11:58
1

What you may do with some changes:

template <typename TReturn, typename...TArgs>
struct SomeFunction{
    using ReturnType = TReturn;
    using ArgTypes = std::tuple<TArgs...>;
};

For Task:

template <typename TFunction, typename TupleArg = typename TFunction::ArgTypes>
class Task;

template <typename TFunction, typename... TArgs>
class Task<TFunction, std::tuple<TArgs...>>
{
public:
    Task(TFunction fun, TArgs...args) : fun_(fun), args_(args...) {}
    void operator()()
    {
        call(make_index_sequence<sizeof...(TArgs)>{});
    }
private:
    template <std::size_t ... Is>
    void call(index_sequence<Is...>)
    {
        fun_(std::get<Is>(args_)...);
    }
private:
    TFunction fun_;
    std::tuple<TArgs...> args_;
};

Live example

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Wow! Exactly what I want. – user3787628 Jul 17 '14 at 11:44
  • This answer only addresses a part of the problem (the easy part), but left out the definition of the call operator which is the hard and ugly part. There is a question on SO for that: http://stackoverflow.com/questions/687490/how-do-i-expand-a-tuple-into-variadic-template-functions-arguments – leemes Jul 17 '14 at 11:45
  • @leemes: `operator()` added (with full example). – Jarod42 Jul 17 '14 at 11:52
  • @leemes not that hard actually. the key part is `fun_(std::get(args_)...)`. Generating a `std::size_T...Indices` only needs several lines of code. – user3787628 Jul 17 '14 at 11:58
  • Yeah but I wonder how you now compare different `Task` objects (with different arguments) for equality like you wanted... – leemes Jul 17 '14 at 11:59