Reputation: 473
I know what a variadic template is in modern C++, but I can't wrap my head around it to make it possible to write code like this:
#include <iostream>
#include <sstream>
using namespace std;
template <typename... Args, typename Combinator>
auto combine(Args... args, Combinator combinator)
{
auto current_value = combinator(args...);
return current_value;
}
int main() {
auto comb = combine(1, "asdf"s, 14.2,
[](const auto& a, const auto& b, const auto& c) {
stringstream ss;
ss << a << "\n";
ss << b << "\n";
ss << c << "\n";
return ss.str();
});
return 0;
}
In other words I want to give an unknown number of arguments of different types to a function, but the last argument to be a lambda or any callable object used to combine the arguments in some way. The example looks purely academic, but building on this example I want to build more funky code, but first I need this to compile. I hope you can help!
I can't make it compile. I don't know what I'm missing.
Here is what GCC spits:
In function 'int main()':
21:6: error: no matching function for call to 'combine(int, std::basic_string<char>, double, main()::<lambda(auto:1&, auto:2&, auto:3&)>)'
21:6: note: candidate is:
7:6: note: template<class ... Args, class Combinator> auto combine(Args ..., Combinator&&)
7:6: note: template argument deduction/substitution failed:
21:6: note: candidate expects 1 argument, 4 provided
Upvotes: 2
Views: 2002
Reputation: 41840
Your code indeed can't compile, because variadic template arguments can only be deduced if they're the last parameters.
To do it without changing your interface, you could do something by like that:
#include <iostream>
#include <sstream>
using namespace std;
template <typename... Args, typename Combinator>
auto combine_impl(Args... args, Combinator combinator) {
return combinator(args...);
}
template <typename... Args>
auto combine(Args... args) {
return combinator_impl<Args...>(args...);
}
int main() {
auto comb = combine(1, "asdf"s, 14.2,
[](const auto& a, const auto& b, const auto& c) {
stringstream ss; ss << a << "\n"; ss << b << "\n"; ss << c << "\n";
return ss.str();
}
);
return 0;
}
But quite frankly, if just do that:
auto comb = [](const auto& a, const auto& b, const auto& c) {
stringstream ss; ss << a << "\n"; ss << b << "\n"; ss << c << "\n";
return ss.str();
}(1, "asdf"s, 14.2);
And if you can't stand calling and declaring a lambda on the same line, you can use C++17's std::invoke
:
auto comb = std::invoke([](const auto& a, const auto& b, const auto& c) {
stringstream ss; ss << a << "\n"; ss << b << "\n"; ss << c << "\n";
return ss.str();
}, 1, "asdf"s, 14.2);
Note that the two last versions are faster than the first solution because they keep value types.
Upvotes: 2
Reputation: 63005
In other words I want to give an unknown number of arguments of different types to a function, but the last argument to be a lambda or any callable object used to combine the arguments in some way.
Since passing the callable as the last argument appears key to your question, here's one approach:
namespace detail {
template<typename TupT, std::size_t... Is>
auto combine(TupT&& tup, std::index_sequence<Is...>) {
return std::get<sizeof...(Is)>(tup)(std::get<Is>(std::forward<TupT>(tup))...);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ last element in tuple is the callable
}
}
template<typename... Ts>
auto combine(Ts&&... ts) {
return detail::combine(
std::forward_as_tuple(std::forward<Ts>(ts)...),
std::make_index_sequence<sizeof...(Ts) - 1>{}
// ^^^^^^^^^^^^^^^^^ arg count = size of pack - 1 (callable)
);
}
This also implements perfect forwarding, which was missing from your question's implementation.
Upvotes: 3
Reputation: 6016
The variadic template must be the last arguments so it can be deduced, see Template argument deduction
Non-deduced contexts
7) The parameter P which is a parameter pack and does not occur at the end of the parameter list:
template<class... Ts, class T> void f1(T n, Ts... args); template<class... Ts, class T> void f2(Ts... args, T n); f1(1, 2, 3, 4); // P1 = T, A1 = 1: deduced T = int // P2 = Ts..., A2 = 2, A3 = 3, A4 = 4: deduced Ts = [int, int, int] f2(1, 2, 3, 4); // P1 = Ts...: Ts is non-deduced context
You should change it to:
#include <iostream>
#include <sstream>
using namespace std;
template <typename... Args, typename Combinator>
auto combine(Combinator combinator, Args&&... args)
{
auto current_value = combinator(std::forward<Args>(args)...);
return current_value;
}
int main() {
auto comb = combine([](const auto& a, const auto& b, const auto& c) {
stringstream ss;
ss << a << "\n";
ss << b << "\n";
ss << c << "\n";
return ss.str();
},
1, "asdf"s, 14.2);
std::cout << comb;
return 0;
}
Upvotes: 9