Last active
June 18, 2024 06:19
-
-
Save texus/8d867996e7a073e1498e8c18d920086c to your computer and use it in GitHub Desktop.
String to lowercase at compile time with with c++14
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This file contains code on how to convert a string to lowercase at compile time. | |
// A large part of the imlementation was taken from http://stackoverflow.com/a/15912824/3161376 which solved the problems that I had in the old implementation. | |
// The string struct will hold our data | |
// The declaration of our string struct that will contain the character array | |
template<char... str> | |
struct string | |
{ | |
// The characters are immediately converted to lowercase when they are put in the array | |
static constexpr const char chars[sizeof...(str)+1] = {((str >= 'A' && str <= 'Z') ? str + ('a' - 'A') : str)..., '\0'}; | |
}; | |
// The definition of the above array | |
template<char... str> | |
constexpr const char string<str...>::chars[sizeof...(str)+1]; | |
// The apply_range exists so that we can create a structure Class<Indices...> where the amount of indices are based on a count value that is passed | |
template<unsigned count, // Amount of indices to still generate | |
template<unsigned...> class meta_functor, // The class to which we will apply the indices | |
unsigned... indices> // Indices that we generated so far | |
struct apply_range | |
{ | |
typedef typename apply_range<count-1, meta_functor, count-1, indices...>::result result; // Add one index and recursively add the others | |
}; | |
template<template<unsigned...> class meta_functor, // The class to which we will apply the indices | |
unsigned... indices> // The generated indices | |
struct apply_range<0, meta_functor, indices...> | |
{ | |
typedef typename meta_functor<indices...>::result result; // Apply the indices to the passed class and get the string that it produced | |
}; | |
// This is where all the things come together and the string is produced. | |
// The lambda_str_type is a struct containing the literal string. | |
// The produce struct is what will be given to apply_range to fill in the indices. | |
// When result which will be returned from apply_range is the one from here which is the lowercase char array. | |
template<typename lambda_str_type> | |
struct string_builder | |
{ | |
template<unsigned... indices> | |
struct produce | |
{ | |
typedef string<lambda_str_type{}.chars[indices]...> result; | |
}; | |
}; | |
// The way to call it in the code is too complex to be used directly in the code. | |
// Calling it from a function is also not possible because then the string is a parameter and not a compile time string literal. | |
// So we use a define to keep the code simple and still put that complex expression directly in the code | |
// Returning the const char* from this function will still happen at runtime, but the actual conversion to lowercase is fully done on compile time. | |
#define TOLOWER(string_literal) \ | |
[]{ \ | |
struct constexpr_string_type { const char * chars = string_literal; }; \ | |
return apply_range<sizeof(string_literal)-1, string_builder<constexpr_string_type>::produce>::result::chars; \ | |
}() | |
// The test code | |
#include <string> | |
int main() | |
{ | |
// Checking performance of using this TOLOWER call (timings are for non-optimized build) | |
// 0.925s (empty loop that is not optimized away also needs time to execute, loops in the code below can't be faster than this one) | |
for (unsigned int i = 0; i < 500000000; ++i) { | |
} | |
// 1s (returning the const char* is done at runtime which is why this loop is slightly slower than an empty loop) | |
for (unsigned int i = 0; i < 500000000; ++i) { | |
const char* s = TOLOWER("HELLO"); | |
} | |
// 7.4s (doing the conversion at runtime) | |
for (unsigned int i = 0; i < 500000000; ++i) { | |
char s[] = "HELLO"; | |
for (unsigned int j = 0; j < 5; ++j) | |
s[j] = ((s[j] >= 'A' && s[j] <= 'Z') ? s[j] + ('a' - 'A') : s[j]); | |
} | |
// When using std::string | |
// 9s (most of the time is used for creating the std::string, conversion itself still happens at compile time) | |
for (unsigned int i = 0; i < 500000000; ++i) { | |
std::string s = TOLOWER("HELLO"); | |
} | |
// 31s (doing the conversion at run runtime while using std::string) | |
for (unsigned int i = 0; i < 500000000; ++i) { | |
std::string s = "HELLO"; | |
for (unsigned int j = 0; j < 5; ++j) | |
s[j] = ((s[j] >= 'A' && s[j] <= 'Z') ? s[j] + ('a' - 'A') : s[j]); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I don't seem to get notifications from comments here. For those who wonder the same in the future: for each unique string a different type is instantiated and each type indeed have their own
static
member.