英文:
Fold expression for a parameter pack with comma operator: How to add additional parameters when expanding the pack?
问题
template<unsigned... Ns>
constexpr auto concatWithSeparator(const char(&...s)[Ns])
{
return CTString<((0U + Ns) * 2 - 1U)>{(s, '|', ...)};
}
英文:
I want to design a compile-time-string class CTString
that can e.g. be constructed from a parameter pack of string literals. This works using a comma-fold-expression (for this toy example, I tried to avoid the use of any system headers to make it self-contained):
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 = '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');
';
}
// 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] == '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');
');
Now I have an additional use case to insert a separator after each literal. How can I expand/fold the parameter pack to insert e.g. the literal "|"
after each element of the pack?
This is my feeble attempt that fails because the expression (s, "|")...
does not work: The comma here just leads to the left operand being discarded:
template<unsigned... Ns>
constexpr auto concatWithSeparator(const char(&...s)[Ns])
{
return CTString<(0U + ... + Ns)>{(s, "|")...};
}
// Compilation error:
constexpr auto cHelloCommaSeparated{ concatWithSeparator("Hello", "World") };
I can work around this problem by introducing a helper class and having the compile-time-string also accept packs of the helper class in its constructors. But I was wondering whether there is neat idiom that I am missing.(I did read and re-read this great article, but to no avail:
C++20 idioms for parameter packs
The code that compiles is here:
Godbolt
Un-comment the last line to see how it fails.
答案1
得分: 4
template<unsigned... Ns>
constexpr auto concatWithSeparator(const char(&...s)[Ns])
{
return CTString<(0U + ... + Ns)>{concat(s, "|").m_chars...};
}
英文:
A simple and pragmatic solution would be to re-use your concat
function:
template<unsigned... Ns>
constexpr auto concatWithSeparator(const char(&...s)[Ns])
{
return CTString<(0U + ... + Ns)>{concat(s, "|").m_chars...};
}
https://godbolt.org/z/hzv5qv6no.
If you want to know how to interleave the parameter pack with the seperator |
, I don't think there is a simple solution. One option is to first create a tuple, whose elements are the parameter pack interleaved with the seperator:
auto tup = std::tuple_cat(
std::tuple<const char(&)[Ns], const char(&)[2] >(s, "|")...
);
As a next step, you would need to translate the tuple back to a parameter pack, which in turn can be passed as argument(s) for the CTString
constructor.
You could do this by forwarding the tuple to a templated helper function using std::index_sequence
. With C++20, you have templated lambdas so that the helper function could be a lambda defined in the body of concatWithSeparator
that you immediately evaluate:
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)>{}
);
}
https://godbolt.org/z/W1jhcrGc5
A slightly more readible version of the second solution would be to use 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, "|"}...)
);
}
答案2
得分: 1
你不能将一个包 arg0, .., argN
转换为 arg0, sep, .., argN, sep
。
作为替代,你可以添加额外的构造函数,以使包的每个元素执行额外的操作:
struct SeparatorTag{};
template<unsigned N>
struct CTString
{
char m_chars[N + 1U];
template<unsigned int NSep, unsigned... Ns>
constexpr CTString(SeparatorTag, const char(&sep)[NSep], const char (&...s)[Ns])
{
auto* p{ m_chars };
((p = CopyN_(s, Ns - 1U, p), p = CopyN_(sep, NSep - 1, p)), ...);
*p = 'struct SeparatorTag{};
template<unsigned N>
struct CTString
{
char m_chars[N + 1U];
template<unsigned int NSep, unsigned... Ns>
constexpr CTString(SeparatorTag, const char(&sep)[NSep], const char (&...s)[Ns])
{
auto* p{ m_chars };
((p = CopyN_(s, Ns - 1U, p), p = CopyN_(sep, NSep - 1, p)), ...);
*p = '\0';
}
// ...
};
';
}
// ...
};
英文:
You cannot transform a pack arg0, .., argN
into arg0, sep, .., argN, sep
.
As alternative, you might add extra constructor to make extra works by element of pack:
struct SeparatorTag{};
template<unsigned N>
struct CTString
{
char m_chars[N + 1U];
template<unsigned int NSep, unsigned... Ns>
constexpr CTString(SeparatorTag, const char(&sep)[NSep], const char (&...s)[Ns])
{
auto* p{ m_chars };
((p = CopyN_(s, Ns - 1U, p), p = CopyN_(sep, NSep - 1, p)), ...);
*p = 'struct SeparatorTag{};
template<unsigned N>
struct CTString
{
char m_chars[N + 1U];
template<unsigned int NSep, unsigned... Ns>
constexpr CTString(SeparatorTag, const char(&sep)[NSep], const char (&...s)[Ns])
{
auto* p{ m_chars };
((p = CopyN_(s, Ns - 1U, p), p = CopyN_(sep, NSep - 1, p)), ...);
*p = '\0';
}
// ...
};
';
}
// ...
};
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论