I'm trying to figure out how to fold only part of the variadic template pack with C++17 fold expressions.
Suppose I'd like to create a compile time "separated string" like "str1_str2_str3_....."
It can be pretty easily done with such a code (just an example):
std::string result;
template<class... Strings>
ConcatString(Strings&... strs)
{
(ConcatString(strs), ...);
}
template <class String>
void ConcatString(String& str)
{
result += str + "_"
}
Then we can execute it like that
std::string s1 = "s1", s2 = "s2", s3 = "s3";
ConcatString(s1, s2, s3);
// result == "s1_s2_s3_"
As you can see there is a problem with last delimiter. Is there any way I can avoid this problem without runtime checks? One solution I can imagine is to fold only (N - 1) args and concat last one "manually".
You could call ConcatString
recursively and use constexpr if to avoid runtime checks
template<typename String, typename... Strings>
void ConcatString(String& str, Strings&... strs) {
if constexpr(sizeof...(strs) == 0) {
result += str;
} else {
result += str + "_";
ConcatString(strs...);
}
}
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With