Skip to content

Instantly share code, notes, and snippets.

@CyberDNIWE
Forked from texus/toLower.cpp
Created June 18, 2024 06:19
Show Gist options
  • Save CyberDNIWE/332ea93fc79db73c93a650793a455b64 to your computer and use it in GitHub Desktop.
Save CyberDNIWE/332ea93fc79db73c93a650793a455b64 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]);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment