10

Answers to Why this program compiles fine in C & not in C++? explain that unlike the C language, the C++ language does not tolerate an initializer string for a char array that is not long enough to hold the terminating null character. Is there a way to specify an unterminated char array in C++ without bloating the string by a factor of four in the source code?

For example, in C and in C++, the following are equivalent:

const char s[] = "Hello from Stack Overflow";
const char s[] = {'H','e','l','l','o',' ','f','r','o','m',' ','S','t','a','c','k',' ','O','v','e','r','f','l','o','w','\0'};

Because the string "Hello from Stack Overflow" has length 25, these produce a 26-element char array, as if the following had been written:

const char s[26] = "Hello from Stack Overflow";
const char s[26] = {'H','e','l','l','o',' ','f','r','o','m',' ','S','t','a','c','k',' ','O','v','e','r','f','l','o','w','\0'};

In C only, a program can exclude the terminating null character, such as if the string's length is known out of band. (Look for "including the terminating null character if there is room" in chapter 6.7.9 of the C99 standard.)

const char s[25] = "Hello from Stack Overflow";
const char s[25] = {'H','e','l','l','o',' ','f','r','o','m',' ','S','t','a','c','k',' ','O','v','e','r','f','l','o','w'};

But in C++, only the second is valid. If I know I will be manipulating the data with functions in the std::strn family, not the std::str family, is there a counterpart in the C++ language to the shorthand syntax of C?

My motivation differs from that of the other question about unterminated char arrays in C++. What motivates this is that several names of items in a game are stored in a two-dimensional char array. For example:

const char item_names[][16] = {
    // most items omitted for brevity
    "steel hammer",
    {'p','a','l','l','a','d','i','u','m',' ','h','a','m','m','e','r'}
};

If there is no shorthand to declare an unterminated char array, then maximum-length names will have to be written character-by-character, which makes them less readable and less maintainable than to shorter names.

Community
  • 1
  • 1
Damian Yerrick
  • 4,602
  • 2
  • 26
  • 64
  • 3
    Why not `string` for you arrays? – edmz Oct 22 '15 at 16:38
  • 1
    @black The answer to your question depends on the answer to the following question: In popular implementations of C++, how much constant overhead does each `std::string` instance have? – Damian Yerrick Oct 22 '15 at 16:46
  • 1
    Negligible in "general". `string_view` is (will be) generally lighter. – edmz Oct 22 '15 at 16:49
  • 2
    Why bother? You're wasting three bytes by zero padding "steel hammer" and gaining only one on palladium. Would an array of pointers to null terminated strings take more or less space? What do you intend to do with the handful of bytes you might save by omitting the terminators? – Alan Stokes Oct 22 '15 at 18:52
  • @AlanStokes It's not just the terminators. It's also that if the strings are of different lengths, you need to store a pointer to each. – Damian Yerrick Oct 06 '20 at 16:12
  • @Damian Welcome back! Note that I asked "Would an array of pointers to null terminated strings take more or less space?" – Alan Stokes Oct 12 '20 at 23:03
  • @AlanStokes The rise of 64-bit computing and the attendant 8-byte size of pointers means each pointer+NUL-terminator is likely to take up more space on average than the slack space at the end of a string. – Damian Yerrick Oct 22 '20 at 22:59

2 Answers2

0

It is possible but I would agree with Alan Stokes' "Why"

For example using C++: Can a macro expand "abc" into 'a', 'b', 'c'? It could be tweaked father to allow operate on any provided array and constrain it by length 16.

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

template <unsigned int N>
constexpr char get_ch (char const (&s) [N], unsigned int i)
{
    return i >= N ? '\0' : s[i];
}

#define STRING_TO_CHARS_EXTRACT(z, n, data) \
    BOOST_PP_COMMA_IF(n) get_ch(data, n)

#define STRING_TO_CHARS(STRLEN, STR)  \
    BOOST_PP_REPEAT(STRLEN, STRING_TO_CHARS_EXTRACT, STR)


const char item_names[][16] = { // most items omitted for brevity
    "steel hammer",
    STRING_TO_CHARS(16, "palladium hammer"),
    //{'p','a','l','l','a','d','i','u','m',' ','h','a','m','m','e','r'},
};

but it could cost you more trouble in a long run...

Community
  • 1
  • 1
VladimirS
  • 600
  • 6
  • 14
0

the disadvantage of c-style array is that it can not be passed to or returned from functions (pass or return char* can give you an array of char* but not array of char[] in the end), we can use std::array to get around this
the advantage of c-style array is that small string literal can be used to initialize larger char[] in a char[][]'s initialize list (as in your example), for std::array we use std::max to simulate this

#include<array>
#include<utility>
#include<algorithm>
#include<iostream>

template<size_t string_length, typename T, T... ints, size_t string_literal_size>
constexpr std::array<char, string_length> generate_string_impl(std::integer_sequence<T, ints...> int_seq, char const(&s)[string_literal_size])
{
    return { {s[ints]...} };
}

template<size_t string_length, size_t string_literal_size>
constexpr std::array<char, string_length> generate_string(char const(&s)[string_literal_size])
{
    return generate_string_impl<string_length>(std::make_index_sequence<string_literal_size - 1>{}, s);
}

template<size_t ...string_literals_size>
constexpr std::array < std::array<char, std::max({ string_literals_size... }) - 1 > , sizeof...(string_literals_size) > generate_array_of_strings(char const(&...s)[string_literals_size])
{
    return { generate_string < std::max({ string_literals_size... }) - 1 > (s)... };
}

int main()
{
    constexpr auto array_of_string1 = generate_array_of_strings("1234", "12345", "123456");
    //std::array<std::array<char,6>,3>{{
    //  {{ '1', '2', '3', '4', '\0', '\0' }},
    //  {{ '1', '2', '3', '4', '5', '\0' }},
    //  {{ '1', '2', '3', '4', '5', '6' }}
    //}}

    //std::array<std::array<char,6>,3> satisfies StandardLayoutType requirement
    char const(&array_of_string2)[std::size(array_of_string1)][6] = reinterpret_cast<char const(&)[std::size(array_of_string1)][6]>(array_of_string1);
    char const(*p_array_of_string2)[std::size(array_of_string1)][6] = reinterpret_cast<char const(*)[std::size(array_of_string1)][6]>(&array_of_string1);

    for (int i = 0; i != 3; ++i)
    {
        for (int j = 0; j != 6; ++j)
        {
            std::cout << i << "," << j << " " << array_of_string2[i][j] << " " << (*p_array_of_string2)[i][j] << std::endl;
        }
    }
}
jhcarl0814
  • 108
  • 2
  • 8