Applying a variadic function with std::apply
Asked Answered
A

1

6

Is it possible to apply a variadic function to a tuple with std::apply?

For example, the following code works fine with GCC 6.2.1:

void print_t(std::string i, std::string j) {
    std::cout << i << " " << j << std::endl;
}

int main() {
        std::tuple<std::string, std::string> t{"ab", "cd"};
        std::experimental::apply(print_t, t);
        return 0;
}

But if I try to apply a variadic function:

template<typename T>
void vprint(T && t) {
    std::cout << std::forward<T>(t) << std::endl;
}

template<typename T, typename ... Ts>
void vprint(T && t, Ts ... ts) {
    std::cout << std::forward<T>(t) << " ";
    vprint<Ts...>(std::forward<Ts>(ts)...);
}

int main() {
        std::tuple<std::string, std::string> t{"fd", "ab"};
        std::experimental::apply(vprint, t);
        return 0;
}

the compiler complains that it cannot deduce template arguments of vprint. OK, let's write them explicitly:

std::experimental::apply(vprint<std::string, std::string>, t);

Now the compiler ends up with some obscure errors which expose standard library internals.

I wrote my own implementation of std::apply in C++11 and I understand why it can't deduce arguments of the variadic function template. But, in theory, std::apply has all the information needed for that deduction.

So is the application of variadic functions a not yet implemented feature in GCC6? Will C++17-compatible compilers allow such application? If not, will they allow application of instantiated variadic template functions, like vprint<std::string, std::string>?

Amyloid answered 10/11, 2016 at 8:48 Comment(1)
why std::apply can't deduce arguments of the variadic function template?Nitrobacteria
T
10

With vprint<std::string, std::string>, you must pass r-value references, so

std::experimental::apply(vprint<std::string, std::string>, std::move(t));

The better way is to use functor (thanks to generic lambda):

std::experimental::apply([](auto&&... args) {
                             vprint(std::forward<decltype(args)>(args)...);
                         },
                         t);
Thirzi answered 10/11, 2016 at 8:57 Comment(3)
Why is the second variant better? std::move is, basically, a cast to rvalue reference, so the semantics of both code snippets seems to be identical.Amyloid
@Sergey: The second one continue to forward argument, whereas the first one forces to know the tuple type, may modify t (as print_t with its pass by copy).Thirzi
@Amyloid We assume print_t isn't the only function you want to call; and that a std::string&& parameter might be freely moved-from. You do not happen to move-from it in your implementation. Semantically, nobody should be relying on the std::string after it is moved-into a function call, however, barring strong guarantees (like get provides).Myriad

© 2022 - 2024 — McMap. All rights reserved.