Skip to content

Instantly share code, notes, and snippets.

@EvanMcBroom
Last active September 1, 2024 05:21
Show Gist options
  • Save EvanMcBroom/d7f6a8fe3b4d8f511b132518b9cf80d7 to your computer and use it in GitHub Desktop.
Save EvanMcBroom/d7f6a8fe3b4d8f511b132518b9cf80d7 to your computer and use it in GitHub Desktop.
Pic and String Literals Part 2

PIC and String Literals Part 2

I previously wrote about how to use macro metaprogramming to simplify using string literals in position independent code (PIC). The results are summarized in the below code snippet and the article can be read on GitHub.

void f() {
    // Example 1: The Pic idiom for instantiating a string
    char picString1[]{ 'a', 'b', 'c' };

    // Example 2: Using a metaprogramming technique from the
    // previous article to achieve the same result
    PIC_STRING(picString2, 5, "abc\n");
}

I resently revisited the topic because I was bothered by the solution's reliance on macro metaprogramming and a third party library to accomplish such a small goal.

Template Metaprogramming

A possible alternative solution was to use template metaprogramming which would likely not require much code or a third party library. Ideally we would use a template function to generate the initializer list of characters to instantiate a local character array at compile time.

The below code is how to create an initializer_list to do just that but there is one issue....

// Return an initializer sequence (ex. {'a', 'b', 'c'})
template <typename T, std::size_t N, std::size_t... Index>
constexpr std::initializer_list<char> make_init_seq(const T(&input)[N], const std::index_sequence<Index...>) {
    return { input[Index]... };
}

// Return an initializer sequence for a given string literal
template <typename T, std::size_t N>
constexpr auto str_literal_init_seq(const T(&input)[N]) {
    constexpr std::make_index_sequence<N> sequence;
    return make_init_seq(std::forward<decltype(input)>(input), sequence);
}

The initializer_list class that is created in make_init_seq will internally store pointers to the start and end of the input array. Ultimately that means that the return statement can not be used in a constant expression, and in turn, neither function as well. Meaning that any string literal used in conjunction with calling str_literal_init_seq will require the string to be stored in the .data section.

Although approaching the problem by using templates to generate an initializer list of character arrays seemed promising at first, creating a solution using this technique was not viable.

The Perfect Solution

Luckily, the previous investigation caused me to stumble across the assembly that is generated for local arrays that are marked as constexpr. What is generated is exactly the PIC assembly that I have been trying to achieve regardless of what compiler optimizations are enabled!

Knowing this, the below local character array initializations generate the same assembly code.

char picString1[]{ 'a', 'b', 'c' };
constexpr char picString2[]{ "abc" };

"Woohoo!" There is no more need to break out a string literal to use it in PIC code. The technique can also be used in a macro function to ensure that the constexpr keyword is not forgotten.

#define pic_string(NAME, STRING) constexpr char NAME[]{ STRING }
#define pic_wstring(NAME, STRING) constexpr wchar_t NAME[]{ STRING }

void f() {
    pic_string(picString, "Hello World!\n");
    std::cout << picString;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment