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.
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.
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;
}