0

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)

tambre
  • 4,625
  • 4
  • 42
  • 55
cmdLP
  • 1,658
  • 9
  • 19

3 Answers3

3

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;
Stephan Lechner
  • 34,891
  • 4
  • 35
  • 58
  • 1
    Not in C++, only in C. –  Jun 03 '17 at 15:26
  • 1
    Despite the wrong tag the question did make it clear from the first revision that it was about C++. An answer that only tells what you can do in C doesn't really answer the question. –  Jun 03 '17 at 15:32
  • 2
    "C++ solution": Let above compile by a C compiler and link the resulting object file to the rest of your code, then just reference it from your C++ code – Daniel Jour Jun 03 '17 at 15:41
  • My first thought was to put it in an extern "C" scope, but it just gave me the same error without. Shouldn't that code in the extern "C" scope be compiled exactly as C-code? – cmdLP Jun 03 '17 at 15:45
  • @hvd: right, but it took some time from noticing this until finding also a proposal for C++-. – Stephan Lechner Jun 03 '17 at 15:51
  • @cmdLP `extern "C"` changes function name mangling behavior, or in other words forces C linkage. Read https://stackoverflow.com/questions/1041866/in-c-source-what-is-the-effect-of-extern-c. It does not mean C code. – Ilja Everilä Jun 03 '17 at 16:05
  • Is there maybe a way like: ``char digits[16] = {"0123456789abcde", [15]='f'};``? The single string in the curly-brackets work and the enumeration of the characters work, too. But why does it not work mixed and how to inline the excess element? – cmdLP Jun 03 '17 at 16:16
2

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.

Tristan Brindle
  • 16,281
  • 4
  • 39
  • 82
  • Even if the program allocated in memory takes 0x2000 bytes in both versions, there are 0x44 more free bytes, which are null, however. – cmdLP Jun 03 '17 at 17:24
  • NOTE: Also the use of ``constexpr auto _digits = "0123456789abcdef"_lit; const char* digits = _digits.str;`` takes less memory than ``const auto _digits = "0123456789abcdef"_lit; #define digits (_digits.str)`` – cmdLP Jun 03 '17 at 17:41
0

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.

Jon Purdy
  • 53,300
  • 8
  • 96
  • 166