使用逗号运算符折叠参数包的表达式:如何在展开包时添加附加参数?

eig*_*eif 7 c++ variadic-templates fold-expression c++20 compile-time-type-checking

我想设计一个编译时字符串类CTString,例如可以从字符串文字的参数包构造。这可以使用逗号折叠表达式(对于这个玩具示例,我试图避免使用任何系统标头以使其独立):

template<unsigned N>
struct CTString
{
    char m_chars[N + 1U];

    template<unsigned... Ns>
    constexpr CTString(const char (&...s)[Ns])
    {
        auto* p{ m_chars };
        ((p = CopyN_(s, Ns - 1U, p)), ...);
        *p = '\0';
    }

    // copy size characters and return one past last copy:
    constexpr char* CopyN_(const char* pFrom, unsigned size, char* pTo)
    {
        for (auto i{ 0U }; i < size; ++i)
            *(pTo++) = *(pFrom++);
        return pTo;
    }
};

template<unsigned... Ns>
constexpr auto concat(const char(&...s)[Ns])
{
    return CTString<(0U + ... + (Ns - 1U))>{s...};
}

constexpr auto cHelloWorld{ concat("Hello", "World") };
static_assert(cHelloWorld.m_chars[9] == 'd');
static_assert(cHelloWorld.m_chars[10] == '\0');
Run Code Online (Sandbox Code Playgroud)

现在我有一个额外的用例,可以在每个文字后面插入分隔符。如何展开/折叠参数包以"|"在包的每个元素后面插入例如文字?这是我微弱的尝试,失败了,因为表达式(s, "|")...不起作用:这里的逗号只会导致左操作数被丢弃:

template<unsigned... Ns>
constexpr auto concatWithSeparator(const char(&...s)[Ns])
{
    return CTString<(0U + ... + Ns)>{(s, "|")...};
}
// Compilation error:
constexpr auto cHelloCommaSeparated{ concatWithSeparator("Hello", "World") };
Run Code Online (Sandbox Code Playgroud)

我可以通过引入一个辅助类并让编译时字符串在其构造函数中接受辅助类包来解决这个问题。但我想知道我是否缺少一些简洁的习惯用法。(我确实阅读并重新阅读了这篇很棒的文章,但无济于事: 参数包的 C++20 习惯用法

编译的代码在这里: Godbolt 取消最后一行的注释以查看它是如何失败的。

joe*_*ech 4

一个简单实用的解决方案是重用您的concat函数:

template<unsigned... Ns>
constexpr auto concatWithSeparator(const char(&...s)[Ns])
{
    return CTString<(0U + ... + Ns)>{concat(s, "|").m_chars...};
}
Run Code Online (Sandbox Code Playgroud)

https://godbolt.org/z/hzv5qv6no


如果您想知道如何将参数包与分隔符交错|,我认为没有简单的解决方案。一种选择是首先创建一个元组,其元素是与分隔符交错的参数包:

auto tup = std::tuple_cat(
    std::tuple<const char(&)[Ns], const char(&)[2] >(s, "|")...
);
Run Code Online (Sandbox Code Playgroud)

下一步,您需要将元组转换回参数包,该参数包又可以作为构造函数的参数传递CTString

您可以通过使用将元组转发到模板化辅助函数来完成此操作std::index_sequence。使用 C++20,您可以使用模板化 lambda,以便辅助函数可以是在concatWithSeparator您立即求值的主体中定义的 lambda:

template<unsigned... Ns>
constexpr auto concatWithSeparator(const char(&...s)[Ns])
{
    auto tup = std::tuple_cat(
        std::tuple<const char(&)[Ns], const char(&)[2] >(s, "|")...
    );
    return [&]<std::size_t ... Is>(std::index_sequence<Is...>)
    {
        return CTString<(0U + ... + Ns)>{std::get<Is>(tup)...};
    }
    (
        std::make_index_sequence<2*sizeof...(Ns)>{}
    );
}
Run Code Online (Sandbox Code Playgroud)

https://godbolt.org/z/W1jhcrGc5


第二个解决方案的更易读的版本是使用std::apply

template<unsigned... Ns>
constexpr auto concatWithSeparator(const char(&...s)[Ns])
{
    return std::apply(
        [&](auto const&... args){ return CTString<(0U + ... + Ns)>(args...); },
        std::tuple_cat(std::tuple<const char(&)[Ns], const char(&)[2] >{s, "|"}...)
    );
}
Run Code Online (Sandbox Code Playgroud)

https://godbolt.org/z/q3GshYx5a