Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++17 split constexpr string on comma and have the number of elements at compile time?

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?

like image 321
user997112 Avatar asked Sep 30 '19 22:09

user997112


2 Answers

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
 }
like image 55
max66 Avatar answered Nov 08 '22 00:11

max66


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",',');.

like image 45
jwimberley Avatar answered Nov 08 '22 01:11

jwimberley