#include <iostream>
#include <vector>
#include <algorithm>
#include <chrono>
#include <set>
#include <ctype.h>

template<int ...Is>
struct IntVector{
    using Type = IntVector<Is...>;
}; 

template<typename T_Vector, int I_New>
struct PushFront;
template<int ...Is, int I_New>
struct PushFront<IntVector<Is...>,I_New> : IntVector<I_New,Is...>{};

template<int I_Size, typename T_Vector = IntVector<>>
struct Iota : 
    Iota< I_Size-1, typename PushFront<T_Vector,I_Size-1>::Type> {};
template<typename T_Vector>
struct Iota<0,T_Vector> : T_Vector{};

template<char C_In>
struct ToUpperTraits {
	enum { value = (C_In >= 'a' && C_In <='z') ? C_In - ('a'-'A'):C_In };
};

template<typename T>
struct TableToUpper;
template<int ...Is>
struct TableToUpper<IntVector<Is...>>{
	static char at(const char in){
		static const char table[] = {ToUpperTraits<Is>::value...};
		return table[in];
	}
};

int tableToUpper(const char c){
	using Table = TableToUpper<typename Iota<256>::Type>;
	return Table::at(c);
}

int myToupper ( int c ){
    if(c >= 'a' && c <='z'){
        return c - ('a'-'A');
    }
    return c;
} 

void __attribute__ ((noinline))test1(const std::string& in,std::string& out){
	std::transform(in.begin(),in.end(),out.begin(),toupper);
}
void __attribute__ ((noinline))test2(const std::string& in,std::string& out){
	std::transform(in.begin(),in.end(),out.begin(),myToupper);
}
void __attribute__ ((noinline))test3(const std::string& in,std::string& out){
	std::transform(in.begin(),in.end(),out.begin(),tableToUpper);
}

int main() {
	std::string s(1024,'.');
	std::iota(s.begin(),s.end(),0);
	//shuffle (s.begin(), s.end(), std::default_random_engine(1));  //shuffel to make input more realistic
	std::string out1(1024,'.');
	std::string out2(1024,'.');
	std::string out3(1024,'.');
 
	std::chrono::nanoseconds time[3]{{},{}};
 
	for(int i=0;i<100;i++){
		const auto t1 = std::chrono::high_resolution_clock::now();
		test1(s,out1);
		const auto t2 = std::chrono::high_resolution_clock::now();
		time[0] += t2 - t1;
		const auto t3 = std::chrono::high_resolution_clock::now();
		test2(s,out2);
		const auto t4 = std::chrono::high_resolution_clock::now();
		time[1] += t4 - t3;
		const auto t5 = std::chrono::high_resolution_clock::now();
		test3(s,out3);
		const auto t6 = std::chrono::high_resolution_clock::now();
		time[2] += t6 - t5;
	}
 
	typedef std::chrono::nanoseconds output_time;
	const char* const out_unit = " ns";
 
	std::cout << "1: " << std::chrono::duration_cast<output_time>(time[0]).count() << out_unit << "\n";
	std::cout << "2: " << std::chrono::duration_cast<output_time>(time[1]).count() << out_unit << "\n";
	std::cout << "3: " << std::chrono::duration_cast<output_time>(time[2]).count() << out_unit << "\n";
	std::cout << out1[100] << std::endl;
	std::cout << out2[100] << std::endl;
	std::cout << out3[100] << std::endl;
	return 0;
}