Skip to content

Instantly share code, notes, and snippets.

@simmplecoder
Last active May 12, 2017 04:16
Show Gist options
  • Save simmplecoder/d278fa5da7f85b488b56a2549363fbee to your computer and use it in GitHub Desktop.
Save simmplecoder/d278fa5da7f85b488b56a2549363fbee to your computer and use it in GitHub Desktop.

std::call

The purpose of std::call is to enable std::apply on non tuple arguments, and make it viable even for multi argument cases.

Current situation:

Lets consider some usage cases:

//first example
template <typename ... Types>
void process_instance(std::size_t id, Types&& ... args);

auto tup = std::make_tuple(1, 2, 3, "numbers");
auto current_instance = (std::size_t)0;
std::invoke(process_instance, current_instance, tup); //will not compile
std::apply(process_instance, id, tup); //ditto

auto complete_tup = std::make_tuple(current_instance, tup); //cumbersome and not always possible
std::apply(process_instance, complete_tup); //will work

//second example
template <typename ... TiedTypes, typename ... Argtypes>
std::tuple<TiedTypes..., ArgTypes...> append_args(const std::tuple<TiedTypes>& tup, ArgTypes&& ... args);

auto tup = std::make_tuple(...);
auto second_half = std::make_tuple(...);
std::invoke(append_args, tup, second_half); //no unpacking, rip
std::apply(append_args, tup, second_half); //too many arguments
//merging into one and then calling apply is not possible

As can be seen from above, the reason is that neither std::invoke nor std::call are finalized to support really generic programming. As a result, it is very hard to specify interfaces and even more hard to use them. The functions doesn't seem to fit well into the concepts the language and library have already created.

Some context: creating another tuple might trigger copy elision, which probably nullifies runtime cost of the usage, but will certainly decrease maintainability and overall programmer performance.

Solution:

The solution is to combine properties of both functions: pass argument unchanged if it is not tuple, and unpack if it is a tuple. The only thing left uncertain is when to unpack tuple, but for now every tuple passed will be unpacked, since it is the most common case.

//first example
std::call(process_instance, current_instance, tup);

//second example
std::call(std::make_tuple(tup), second_half);
//                           ^^ probably will be elided

As can be seen from examples, std::call will simplify already possible metaprogramming and enable other forms of it.

Specification:

Signature:

template <typename Callable, typename ... ArgTypes>
R call(Callable&& callable, ArgTypes&& ... args);

Behavior:

For each type in ArgTypes, if type is std::tuple, it should be unpacked. The reference category should stay the same for the unpacked elements (e.g. if tuple had rvalue reference, the unpacked elements has to have rvalue reference as well). cv-qualifiers has to be preserved during unpacking as well.

Return type has to be deduced on argument list after all unpacking.

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