cmdLP
cmdLP

Reputation: 1856

String-literals without escaping null-character

When I built a function which gives the hexadecimal representation of a nibble (4 bits) and I looked at the binary file, for the lookuptable of the digits, there was an additional 0-char even if it was not used.

const char digits[] = "0123456789abcdef";

I know that you can write it in form of an array:

const char digits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

But that would take a while to write and use more disk-space for other numerical-systems with more digits. But is there any way to write it as a literal, but without the null-character at the end?

(I am using Clang with -std=c++14)

Upvotes: 0

Views: 639

Answers (3)

Jon Purdy
Jon Purdy

Reputation: 54999

This might be overkill, but if it’s acceptable to use std::array, then you can statically build one from a string literal using constexpr functions:

#include <array>
#include <iostream>

// A type-level list of indices.

template <size_t... Xs>
struct indices { using type = indices<Xs..., sizeof...(Xs)>; };

// Generate indices from 0 to N.

template <std::size_t N>
struct upto { using type = typename upto<N - 1>::type::type; };

template <>
struct upto<0> { using type = indices<>; };

// Make an array by assigning each character
// from the corresponding index in the source.

template <std::size_t... X, typename A = std::array<char, sizeof...(X)>>
constexpr A make_array(char const *const source, const indices<X...>&) {
    return A{{source[X]...}};
}

// A convenience function that deduces the size.

template <std::size_t N>
constexpr std::array<char, N - 1> string_constant(char const (&data)[N]) {
    return make_array(&data[0], typename upto<N - 1>::type{});
}

int main() {
    constexpr auto s = string_constant("123456");
    std::cout << sizeof(s) << '\n';
}

Now sizeof(s) is 6, and if you look at the generated assembly, the string literal is stored as .ascii, not .asciz, so there is no trailing null character. You can use the member functions of std::array such as size(), begin(), and end(), and can cast &s[0] to const char * to access character data directly.

Upvotes: 0

Tristan Brindle
Tristan Brindle

Reputation: 16824

I have no idea why you'd want to do such a thing, but if you can cope with using a compiler extension, Clang and GCC will let you write a templated user-defined literal operator which will chop off the trailing null:

template <typename CharT, std::size_t N>
struct string_literal {
    static constexpr std::size_t size = N;
    const CharT str[N];
};

template <typename CharT, CharT... Str>
constexpr string_literal<CharT, sizeof...(Str)> operator"" _lit()
{
    return {{ Str... }};
}

int main()
{
    constexpr auto s = "test"_lit;
    static_assert(s.size == 4);
    static_assert(s.str[0] == 't'); // etc
}

(Returning a std::array<const char, N> is another option.)

I've no idea whether this is what you're after, but then I don't really understand the motivation to be honest -- even back in the 70s the designers of C weren't too worried about "wasting" a single byte in string literal.

Upvotes: 2

Stephan Lechner
Stephan Lechner

Reputation: 35154

In C, you could write

const char digits[16] = "0123456789abcdef";

This is actually supported in C as defined in array initialization:

If the size of the array is known, it may be one less than the size of the string literal, in which case the terminating null character is ignored: char str[3] = "abc"; // str has type char[3] and holds 'a', 'b', 'c'

For C++, I see no direct way; but one could come around this as follows:

char digits_t[16] = "0123456789abcde";
digits_t[15]='f';    
const char* digits = digits_t;

Upvotes: 3

Related Questions