Is it possible to fold only part of the pack with C++17 fold expressions?
No, a fold expression will fold over the entire pack. However, we can do some tricks to achieve what we need.
Here, your Concat
function can be simplified to use a single binary fold.
template<class String, class... Strings>
std::string Concat(const std::string& delimiter, const String& str, const Strings&... strs)
{
return (str + ... + (delimiter + strs));
}
Usage:
int main()
{
std::cout << Concat(",", "a") << std::endl;
std::cout << Concat(",", "a", "b") << std::endl;
std::cout << Concat(",", "a", "b", "c") << std::endl;
}
Output:
a
a,b
a,b,c
The trick here is that we split the parameter pack into a singular "head" (str
) and a variadic "tail" (strs
). In this way we let the function parameter list pull the first element off the pack. (A lot of C++11 style template metaprogramming used this trick).
Another approach to take would be to create a set of indices 0, 1, ..., N for our parameter pack and then for our fold logic we could do something special for the 0th, Nth, or even an arbitrary element. You can find varieties of this approach on the pretty print tuple question.
In C++20 thanks to template lambdas in C++20 we can move all the logic into a single method like so:
template<class... Strings>
std::string Concat(const std::string& delimiter, const Strings&... strs)
{
return [&delimiter]<class Tup, size_t... I> (const Tup& tuple, std::index_sequence<I...>)
{
return (std::string{} + ... + (I == 0 ? std::get<I>(tuple) : delimiter + std::get<I>(tuple)));
}(std::tie(strs...), std::make_index_sequence<sizeof...(strs)>{});
}
That ugly syntax is me creating a lambda and calling it in a single statement. You may arguably make it more readable calling it via std::invoke
instead.
Note that we use a check on the index for whether to print the delimiter or not.
Let's use the index checking trick to only concat every other with the delimiter:
template<class... Strings>
std::string ConcatEveryOther(const std::string& delimiter, const Strings&... strs)
{
return [&delimiter]<class Tup, size_t... I> (const Tup& tuple, std::index_sequence<I...>)
{
return (std::string{} + ... + (I % 2 == 0 ? std::get<I>(tuple) : delimiter + std::get<I>(tuple)));
}(std::tie(strs...), std::make_index_sequence<sizeof...(strs)>{});
}
Now std::cout << ConcatEveryOther(",", "a", "b", "c", "d", "e", "f", "g") << std::endl;
will give us an output like
a,bc,de,fg