有没有可能用C++17的fold表达式只折叠包的一部分?

c7rzv4ha  于 2023-03-25  发布在  其他
关注(0)|答案(3)|浏览(162)

我试图弄清楚如何使用C++17折叠表达式折叠可变参数模板包的一部分。
这可以很容易地用这样的代码完成(只是一个例子):

std::string result;

template<class... Strings>
ConcatString(Strings&... strs)
{
    (ConcatString(strs), ...);
}

template <class String>
void ConcatString(String& str)
{
     result += str + "_"
}

然后我们就可以那样执行了

std::string s1 = "s1", s2 = "s2", s3 = "s3";
ConcatString(s1, s2, s3);
// result == "s1_s2_s3_"

正如你所看到的,最后一个分隔符有一个问题。有没有什么方法可以避免这个问题而不需要运行时检查?我可以想象的一个解决方案是只折叠(N - 1)个args并“手动”concat最后一个。

dxxyhpgq

dxxyhpgq1#

您可以递归调用ConcatString并使用constexpr if来避免运行时检查

template<typename String, typename... Strings>
void ConcatString(String& str, Strings&... strs) {
    if constexpr(sizeof...(strs) == 0) {
        result += str;
    } else {
        result += str + "_";
        ConcatString(strs...);
    }
}
qzwqbdag

qzwqbdag2#

有没有可能用C++17的fold表达式只折叠包的一部分?
不,一个折叠表达式将折叠整个包。然而,我们可以做一些技巧来实现我们所需要的。
在这里,您的Concat函数可以简化为使用单个二进制折叠。

template<class String, class... Strings>
std::string Concat(const std::string& delimiter, const String& str, const Strings&... strs)
{
     return (str + ... + (delimiter + strs));
}

用法:

int main()
{
    std::cout << Concat(",", "a") << std::endl;
    std::cout << Concat(",", "a", "b") << std::endl;
    std::cout << Concat(",", "a", "b", "c") << std::endl;
}

输出:
项目a
a、B
a、B、c

这里的技巧是,我们将参数包分成一个单一的“头”(str)和一个可变的“尾”(strs)。这样,我们让函数参数列表从包中取出第一个元素。(很多C11风格的模板元编程都使用了这个技巧)。
另一种方法是为我们的参数包创建一组索引0、1、...、N,然后对于折叠逻辑,我们可以为第0个、第N个甚至是任意元素。你可以在pretty print tuple问题中找到各种各样的这种方法。在C
20中,感谢C++中的模板lambda我们可以把所有的逻辑移到一个方法中,就像这样:

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)>{});
}

Demo 2

这个丑陋的语法是我创建了一个lambda,并在一个语句中调用它。你可以通过std::invoke调用它来提高它的可读性。
请注意,我们对索引进行了检查,以确定是否打印分隔符。
让我们使用索引检查技巧,只使用分隔符连接每一个其他的:

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)>{});
}

现在std::cout << ConcatEveryOther(",", "a", "b", "c", "d", "e", "f", "g") << std::endl;将给予如下输出
a、bc、de、fg
∮ ∮ ∮ ∮ ∮

des4xlb0

des4xlb03#

不如这样:

template<typename Last>
std::string concat(Last last) {
    return last;
}

template<typename First, typename ...Rest>
std::string concat(First first, Rest ...rest) {
    return first + std::string {"_"} + concat(rest...);
}

std::string s1{"foo"}, s2{"bar"}, s3{"baz"};
std::cout << concat(s1, s2, s3); // Prints "foo_bar_baz"

编辑:我把函数做得更简单了:

template <typename First, typename... Rest>
std::string concat(First first, Rest... rest) {
    return (first + ... + (std::string {"_"} + rest));
}

相关问题