Skip to content

Instantly share code, notes, and snippets.

@texus
Last active June 18, 2024 06:19
Show Gist options
  • Save texus/8d867996e7a073e1498e8c18d920086c to your computer and use it in GitHub Desktop.
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 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]);
}
}
@texus
Copy link
Author

texus commented Dec 14, 2019

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment