How to create a constexpr array with a sequence of string_views at compile time?

醉酒当歌 提交于 2021-01-27 06:12:06

问题


I would like to create a constexpr std::array<std::string_view, ConstexprNumber>. It should for example contain constexpr std::strings_view's like this:

"text0", "text1", "text2", ..... "textn"

I came up with the following initial solution:

#include <iostream>
#include <array>
#include <utility>
#include <string>
#include <string_view>

// Number of strings that we want to generate
constexpr size_t NumberOfTextsToGenerate = 10u;

// constexpr function to build a string
constexpr std::string_view makeString(unsigned int i) {
    return std::string_view("text");
}

// Helper: constexpr function that will create an array of string_views and initialize it
template <unsigned int... ManyIntegers>
constexpr auto generateTextHelper(std::integer_sequence<unsigned int, ManyIntegers...>) {
    return std::array<std::string_view, sizeof...(ManyIntegers)>{ {makeString(ManyIntegers)...}};
}

// Helper: constexpr function that will return an array of string_views as shown above with a specified number of texts
constexpr auto generateTextArray() {
    return generateTextHelper(std::make_integer_sequence<unsigned int, NumberOfTextsToGenerate>());
}

// This is a definition of a std::array<std::string_view,UpperBound> initialized with some text
constexpr auto text = generateTextArray();

int main() {
    for (size_t i{}; i < NumberOfTextsToGenerate; ++i)
        std::cout << text[i] << '\n';
    return 0;
}

But, of course the "makeString" function does not do what I want. Or, better said, I do not know how to implement the correct solution.

How can we get such an array to work? Either based on this initial solution or something similar?


回答1:


UPDATE: Another solution through STL.

The below solution is through boost's preprocessing macros.


Well, it took me three hours, but I still couldn't do it successfully through STL, so I gave up and eventually I went back to the macro.

If you don't want to import the whole boost library, you can just separate out these BOOST_PP macros into your project, although they are still very large, so it might take a bit of your time.

Here is the code (link to godbolt):

#include <iostream>
#include <string_view>
#include <experimental/array>

#include <boost/preprocessor/punctuation/comma_if.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>


int main()
{
    constexpr auto strs_array = std::experimental::make_array<std::string_view>
    (
#define ADD_STRING(z, index, data)    BOOST_PP_COMMA_IF(index) data # index
        BOOST_PP_REPEAT(20, ADD_STRING, "test")
#undef ADD_STRING
    );

    for (const std::string_view &str : strs_array) {
        std::cout << str << '\n';
    }
}

The maximum number allowed to be generated is BOOST_PP_LIMIT_REPEAT (currently is 256), which means you can currently generate up to "test255", I guess that's enough.

Output:

Program returned: 0
Program stdout
test0
test1
test2
test3
test4
test5
test6
test7
test8
test9
test10
test11
test12
test13
test14
test15
test16
test17
test18
test19



回答2:


Woohoo, I finally did it!!!

(Link to godbold)

#include <iostream>
#include <array>
#include <utility>
#include <string>
#include <string_view>


template <class T>
using const_c_str_char_t = std::remove_const_t<std::remove_pointer_t<T>>;

template <auto str1, auto str2, size_t ...indexes1, size_t ...indexes2>
constexpr decltype(auto) string_append_sequence(std::index_sequence<indexes1...>, std::index_sequence<indexes2...>)
{
    using char_type = const_c_str_char_t<decltype(str1())>;
    static_assert(std::is_same_v<char_type, const_c_str_char_t<decltype(str2())>>);

    return std::integer_sequence<char_type, str1()[indexes1]..., str2()[indexes2]...>{};
}

template <class T, T ...values1, T ...values2>
constexpr decltype(auto) append_sequence(std::integer_sequence<T, values1...>, std::integer_sequence<T, values2...>) {
    return std::integer_sequence<T, values1..., values2...>{};
}


template <class sequence_t>
struct string_sequence_to_view;

template <class char_type, char_type ...chars>
struct string_sequence_to_view<std::integer_sequence<char_type, chars...>>
{
    using string_view_t = std::conditional_t<std::is_same_v<char_type, char>, std::string_view, std::wstring_view>;

    static constexpr decltype(auto) get() {
        return string_view_t{c_str};
    }

    static constexpr const char_type c_str[]{chars..., char_type{}};
};

template <class char_type, size_t value, std::enable_if_t<std::is_same_v<char_type, char> || std::is_same_v<char_type, wchar_t>, int> = 0>
constexpr decltype(auto) integer_to_string_sequence()
{
    constexpr auto digits = []()
    {
        if constexpr (std::is_same_v<char_type, char>) {
            return "0123456789abcdefghijklmnopqrstuvwxyz";
        }
        else if constexpr (std::is_same_v<char_type, wchar_t>) {
            return L"0123456789abcdefghijklmnopqrstuvwxyz";
        }
    };

    constexpr size_t remainder = value % 10;
    constexpr size_t next_value = value / 10;

    if constexpr (next_value != 0) {
        return append_sequence(integer_to_string_sequence<char_type, next_value>(), std::integer_sequence<char_type, digits()[remainder]>{});
    }
    else {
        return std::integer_sequence<char_type, digits()[remainder]>{};
    }
}

#define INT_TO_C_STR(char_type, num)    string_sequence_to_view<decltype(integer_to_string_sequence<char_type, num>())>{}.c_str

#define APPEND_C_STR_AS_VIEW(s1, s2)                                            \
    string_sequence_to_view<                                                    \
        decltype(                                                               \
            string_append_sequence<                                             \
                [] { return s1; },                                              \
                [] { return s2; }                                               \
            >(                                                                  \
                std::make_index_sequence<sizeof(s1) / sizeof(s1[0]) - 1>(),     \
                std::make_index_sequence<sizeof(s2) / sizeof(s1[0]) - 1>()      \
            )                                                                   \
        )                                                                       \
    >{}.get()

// Number of strings that we want to generate
constexpr size_t NumberOfTextsToGenerate = 20u;

// constexpr function to build a string
template <size_t i>
constexpr std::string_view makeString() {
    return APPEND_C_STR_AS_VIEW("test", INT_TO_C_STR(char, i));
}

template <size_t i>
constexpr std::wstring_view makeStringW() {
    return APPEND_C_STR_AS_VIEW(L"test", INT_TO_C_STR(wchar_t, i));
}

// Helper: constexpr function that will create an array of string_views and initialize it
template <size_t... ManyIntegers>
constexpr auto generateTextHelper(std::integer_sequence<size_t, ManyIntegers...>) {
    return std::array<std::string_view, sizeof...(ManyIntegers)>{ makeString<ManyIntegers>()...};
}

template <size_t... ManyIntegers>
constexpr auto generateTextHelperW(std::integer_sequence<size_t, ManyIntegers...>) {
    return std::array<std::wstring_view, sizeof...(ManyIntegers)>{ makeStringW<ManyIntegers>()...};
}

// Helper: constexpr function that will return an array of string_views as shown above with a specified number of texts
constexpr auto generateTextArray() {
    return generateTextHelper(std::make_integer_sequence<size_t, NumberOfTextsToGenerate>());
}

constexpr auto generateTextArrayW() {
    return generateTextHelperW(std::make_integer_sequence<size_t, NumberOfTextsToGenerate>());
}

// This is a definition of a std::array<std::string_view,UpperBound> initialized with some text
constexpr auto text = generateTextArray();
constexpr auto textW = generateTextArrayW();

int main()
{
    for (size_t i{}; i < NumberOfTextsToGenerate; ++i) {
        std::cout << text[i] << '\n';
    }

    for (size_t i{}; i < NumberOfTextsToGenerate; ++i) {
        std::wcout << textW[i] << L'\n';
    }

    return 0;
}

Output:

test0
test1
test2
test3
test4
test5
test6
test7
test8
test9
test10
test11
test12
test13
test14
test15
test16
test17
test18
test19

EDIT: Supported wchar_t string.




回答3:


You can do the following:

constexpr size_t NumberOfTextsToGenerate = 10u;

template <std::size_t I>
struct digit_to_end_impl {
    static constexpr const char value[]{ 't', 'e', 'x', 't', (I + '0'), 0 };
};

template <std::size_t I>
struct digit_to_end {
    static constexpr std::string_view value = digit_to_end_impl<I>::value;
};

template <std::size_t I>
constexpr std::string_view makeString() {
    return digit_to_end<I>::value;
}

template <unsigned int... ManyIntegers>
constexpr auto generateTextHelper(std::integer_sequence<unsigned int, ManyIntegers...>) {
    return std::array<std::string_view, sizeof...(ManyIntegers)>{ { makeString<ManyIntegers>()... } };
}

// ...

Check out the DEMO.

However, this won't work when NumberOfTextsToGenerate is more than 10.




回答4:


I now found a solution that finally satisfies my needs.

Because of many good answers by SO users and additional answers to my question here,

I will now use the following code.

#include <iostream>
#include <algorithm>
#include <iterator>
#include <array>
#include <string>

// Specification for the output that we want to generate
constexpr const char BaseString[]{ "text" };    // Some example text
constexpr size_t StartIndex = 1u;               // Start index. So first array element will be "Text1"
constexpr size_t NumberOfElements = 20u;        // Number of elements to create. Last array element will be "Text20"

// These templates are used to generate a std::array
namespace ConstexprGenerator {
    // To create something like "text123" as constexpr
    template <const size_t numberToConvert, const char* Text>
    class Converter {
    public:
        // Some helper variables for calculating sizes
        static constexpr size_t TextLength{ std::char_traits<char>::length(Text) };
        static constexpr size_t NumberOfDigits{ ([]() constexpr noexcept {size_t result = 0; int temp = numberToConvert; for (; temp != 0; temp /= 10) ++result; return result; }()) };
        static constexpr size_t ArrayLength{ (numberToConvert ? 1u : 2u) + NumberOfDigits + TextLength };

        // Here we will store the string
        std::array<char, ArrayLength> internalString{};

        // Constructor: Copy text and Convert number to character digits
        constexpr Converter() noexcept {
            size_t i{ 0 };  for (; i < TextLength; ++i) internalString[i] = Text[i]; // Copy text
            if (numberToConvert == 0) internalString[i] = '0';  // In case that the given number is 0, then simply copy '0' character   
            else {
                i = NumberOfDigits + TextLength - 1;            // Convert number to character digits
                int number = numberToConvert; for (; number; number /= 10)
                    internalString[i--] = number % 10 + '0';
            }
        }
        constexpr std::array<char, ArrayLength> get() const { return *this; };              // getter
        constexpr operator std::array<char, ArrayLength>() const { return internalString; } // type cast
    };

    // Templated variable. Will have always a different type, depending on the template parameters
    template<const size_t numberToConvert, const char* Text>
    constexpr auto Converted = Converter<numberToConvert, Text>{}.get();

    // Generate a std::array with n elements that consist of const char *, pointing to Textx...Texty
    template <int... ManyIntegers>
    constexpr auto generateTextHelper(std::integer_sequence<size_t, ManyIntegers...>) noexcept {
        return std::array<const char*, sizeof...(ManyIntegers)>{ {Converted<ManyIntegers + StartIndex, BaseString>.data()...}};
    }
    // Generate the required number of texts
    constexpr auto generateTextArray()noexcept {
        return generateTextHelper(std::make_integer_sequence<size_t, NumberOfElements>());
    }
}
// This is a constexpr array
constexpr auto text = ConstexprGenerator::generateTextArray();

int main() {
    std::copy(text.begin(), text.end(), std::ostream_iterator<const char*>(std::cout, "\n"));
    return 0;
}

Tested with MSVC, Clang and gcc



来源:https://stackoverflow.com/questions/65664672/how-to-create-a-constexpr-array-with-a-sequence-of-string-views-at-compile-time

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!