I have a comma-separated list of fields represented by a char array at compile time:
constexpr static char arrayStr[] = "a,b,c";
I would like to split on "," and have the number of elements available at compile time. Pseudo code:
constexpr static size_t numFields = SPLIT(arrayStr, ",");
would return 3.
Is there any way to achieve this using C++17?
I would like to convert the char array to string, then split on "," and have the number of elements available at compile time.
If for "string" do you mean "std::string
", it isn't constexpr
so it's incompatible with a compile time computation.
If for "string" you accept the C-style string, a char const *
, and if you're interested in separators of a single char
, you can try something as follows
#include <iostream>
constexpr static char arrayStr[] = "a,b,c";
constexpr std::size_t SPLIT (char const * str, char sep)
{
std::size_t ret { 1u };
while ( *str )
if ( sep == *str++ )
++ ret;
return ret;
}
int main ()
{
constexpr auto numFields = SPLIT(arrayStr, ',');
std::cout << numFields << std::endl; // print 3
}
Using a templated function that accepts a fixed length array by reference, templated on the length of the array:
#include <iostream>
#include <array>
constexpr char arrayStr[] = "a,b,c";
template<size_t N>
constexpr size_t numFields(const char(&arrayStr)[N], char delim) {
size_t count = 1;
for (const auto& ch : arrayStr) {
if (ch == delim) {
++count;
}
}
return count;
}
using namespace std;
int main(int argc, char *argv[]) {
array<string,numFields(arrayStr,',')> x;
cout << x.size() << endl;
}
Templating arrows arrayStr to be a fixed-size array argument, allowing the range-based for loop.
EDIT
The OP asked in the comments about creating a class at compile time whose members include the string literal and its tokenization count (something was also mentioned about static class members, but I'm not clear on the use case). This was trickier! After some work, the above numFields
function can be used together with something like this:
class Foo {
public:
template<typename T>
constexpr Foo(T&& str, char delim)
: _array(std::forward<T>(str)),
_count(numFields(_array,delim)) {
}
auto data() const {
return _array;
}
size_t size() const {
return _count;
}
private:
const char (&_array)[N];
const size_t _count;
};
template<typename T>
constexpr auto wrapArray(T&& str, char delim) -> Foo<sizeof(str)> {
return Foo<sizeof(str)>(std::forward<T>(str),delim);
}
constexpr auto wrappedArrayStr = wrapArray("a,b,c",',');
using namespace std;
int main(int argc, char *argv[]) {
cout << wrappedArrayStr.size() << endl;
cout << wrappedArrayStr.data() << endl;
}
I'm not certain that the perfect forwarding here is necessary, but I use it to forward the string literal argument to the class member. The helper function wrapArray
prevents having to double-paste all the compile time string literals, i.e. avoiding constexpr Foo<sizeof("a,b,c")> wrappedArrayStr("a,b,c",',');
.
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