SugarPP: syntactic 🍬 for programming in C++
SugarPP is a collection of syntactic sugar for C++ code.
How to Use
-
SugarPP is header only and each header file is independent. Just clone this repository and go to ./include/sugarpp or copy the corresponding header file you want to use.
Or, if you want to use it hassle free, copy this Cmake snippet to your root
CMakeLists.txtto automatically download & use this library in your project. No need to clone the project manually!include(FetchContent) FetchContent_Declare( SugarPP GIT_REPOSITORY https://github.com/HO-COOH/SugarPP.git GIT_TAG origin/master ) FetchContent_MakeAvailable(SugarPP) #Use for your target add_executable(<Your target> main.cpp) target_link_libraries(<Your target> PRIVATE SugarPP)
Or, if you want to use the library globally, copy this Cmake snippet to your roor
CMakeLists.txtto automatically download & use this library in your project. No need to clone the project!include(FetchContent) FetchContent_Declare( SugarPP GIT_REPOSITORY https://github.com/HO-COOH/SugarPP.git GIT_TAG origin/master ) FetchContent_MakeAvailable(SugarPP) #Use globally link_libraries(SugarPP)
-
Then add
#include <sugarpp/xxx/xxx.hpp>. Also Note: Everything in SugarPP is now insidenamespace SugarPP! So you may wantusing namespace SugarPP;
You can find quick documentation for every modules in ./docs
You can find examples for every modules in ./test/source
Alternatively, see generated doxygen document here.
Requirements
SugarPP uses various C++17 language features; thus, it requires a C++17 compatible compiler to use.
GCC 10.1 and older has a known bug, which causes issues on the overload resolution of Nope, it is compatible with GCC 9.2 now :)detail::when_impl; consider upgrading to GCC 10.2 or newer
Tested with:
- GCC 10.2 & GCC 9.2
- Clang 10.0
- Visual Studio 16.7
Features
Kotlin-style when syntax
Kotlin has the when expression for matching values,
replacing the traditional switch/case in most languages.
C/C++'s native switch/case has the drawback of only
matching integer values.
SugarPP when
- Value matching (for any comparable type, not just integers):
/* Kotlin */ when (x) { 1 -> print("x == 1") 2 -> print("x == 2") else -> { // Note the block here print("x is neither 1 nor 2") } }
/* SugarPP */ when (x, 1, []{ print("x == 1"); }, 2, []{ print("x == 2"); }, Else(), []{ print("x is neither 1 nor 2"); } )(); //returns a function object, use () to call it
- Type matching:
/*kotlin*/ fun describe(obj: Any): String = when (obj) { 1 -> "One" "Hello" -> "Greeting" is Long -> "Long" !is String -> "Not a string" else -> "Unknown" }
/*SugarPP*/ auto describe = [](auto&& obj) { return when(obj, 1, "One", "hello", "Greeting", is<long>(), "long", is_not<const char*>(), "Not a string", Else(), "Unknown string" ); };
- Polymorphic type matching:
/*SugarPP*/ struct Shape { virtual ~Shape() = default; }; struct Circle :Shape {}; struct Square :Shape{}; std::unique_ptr<Shape> pt{ new Circle{} }; when(*pt, is_actually<Circle>(), [] { print("Circle* pt"); }, is_actually<Square>(), [] { print("Square* pt"); }, Else(), [] { print("Unknown type"); } )(); //"Circle* pt"
- Range matching:
/*kotlin*/ val validNumbers = arrayOf(11, 13, 17, 19) when (x) { in 1..10 -> print("x is in the range") in validNumbers -> print("x is valid") !in 10..20 -> print("x is outside the range") else -> print("none of the above") }
/*SugarPP*/ std::array validNumbers{11,13,17,19}; when(x, Range(1, 10), []{ print("x is in the range"); }, Range(validNumbers),[]{ print("x is valid"); }, NOT{Range(10, 20)}, []{ print("x is outside the range"); }, Else(), []{ print("none of the above"); } )();
- Argument-less switches
/*kotlin*/ when { x.isOdd() -> print("x is odd") y.isEven() -> print("y is even") else -> print("x+y is even.") }
/*SugarPP*/ int x = 1, y = 2; when( isOdd(x), []{ print("x is odd"); }, isEven(y), []{ print("y is even"); }, Else(), []{ print("x+y is even");} )();//"x is odd"
Note: Unlike C/C++ switch-case, kotlin when is short-circuiting; the execution terminates at the first satisfied branch. SugarPP when has the same behavior.
- Pattern matching
Kotlin doesn't seems to support _ as a place holder.
/*Rust*/ for i in 1..=100 { match (i % 3, i % 5) { (0, 0) => println!("FizzBuzz"), (0, _) => println!("Fizz"), (_, 0) => println!("Buzz"), (_, _) => println!("{}", i), } }
/*SugarPP*/ for(auto i:Range(1, 101)) { when(std::tuple{ i % 3, i % 5 }, std::tuple{ 0, 0 }, [] { print("fizzbuzz"); }, std::tuple{ 0, _ }, [] { print("fizz"); }, std::tuple{ _, 0 }, [] { print("buzz"); }, Else(), [i] { print(i); } )(); }
Usage
Just copy ./include/sugarpp/when/when.hpp and add #include "when.hpp" in your project.
Documentation
See docs/When.md
Read More
At the time of writing this library, I was not aware of the C++23 pattern matching proposal. And yes, SugarPP::when will have performance penality compared with what can be done with switch-case statement. SugarPP::when works as recursively comparing the condition to each branch, so I am not sure whether this has performance penalty compared with the pattern matching proposal.
As I am still an early learner, I will update this part to give you more insight. You can find the original implementation of that proposal here
Simpler IO
IO in C++ should work how you expect it to. SugarPP's IO functions are simple
and much more intuitive than native C++ IO. No more messing with getchar() and
getline() nonsense, and print() anything!
Features
The input template function is similar to Python's input. It prints a prompt message
and does automatic error handling - for example, if the input is bad, it will
clear the bad bit and re-prompt until an acceptable input is given (this behavior can
be disabled).
- If the type is a primitive, the function will work the same as
std::cin >>- If the type given is
unsigned,inputwill automatically convert the input to an absolute value.
- If the type given is
- If the type is
std::string, it will behave the same asstd::getline, getting the whole line at once.
auto name = input<std::string>("Enter your name: "); print("Hello,", name, "How old are you?"); auto age = input<int>("Enter your age: "); print(name, "is", age, "years old");
The print function also behaves similar to Python's print; it can print any number of arguments of any type, separated by a specified delimiter (defaulting to space). SugarPP's print can print almost anything:
- Anything
std::couthas an overload for - Anything that is iterable (i.e. has a
.begin()or can be called withstd::begin) - Nested iterables (at any depth)
std::tupleandstd::pairboolwill be printed asTrueorFalse
printLn behaves similarly, but prints each argument on a new line.
/*print any iterable*/ std::array arr{ 1,2,3 }; print(arr); //[1, 2, 3] /*print a tuple*/ std::tuple t{ "SugarPP", 123, 45.6f }; print(t); //(SugarPP, 123, 45.6) /*print any nested printable*/ std::vector<std::vector<int>> v1{ {1,2,3}, {5,6,7,8}, {9,10} }; std::vector<std::vector<std::vector<int>>> v2{ {{1,2,3}, {5,6,7,8}, {9,10}}, {{10,11},{12,13}, {}} }; printLn(v1, v2); //[[1, 2, 3], [5, 6, 7, 8], [9, 10]]... /*print a bool*/ print(0.1 + 0.2 == 0.3); //"False", you should know why :P
There are additional ThreadSafe versions of these functions with the same name, under namespace ThreadSafe.
Usage
Just copy ./include/sugarpp/io/io.hp and add #include "io.hpp".
More examples in ./test/source/io/io.cpp.
Documentation
See docs/IO.md.
Range
Use numerical ranges to simplify your range-based for loop!
Not to be confused with C++20 ranges. Container Ranges are working in progress towards providing C++20 ranges functionality in C++17.
Many other programming languages have a range syntax for iteration:
// e.g. in Rust for n in 0..10 { println!(n); }
# or in Python for i in range(0, 10): print(i)
Features
SugarPP defines 3 types of Ranges in some sort of "class overloading" way
-
Numerical ranges
start,end, andstep (default = 1)with a C++ foreach loop. Type will be inferred and automatically converted if needed.for (auto i : Range(2.0, 10.0, 3)) print(i); /* 2 5 8 */
- Multiple-dimension ranges
for (auto [i, j] : Range(-5, 1) | Range(0, 3)) print(i, '\t', j); /* -5 0 -5 1 -5 2 -4 0 -4 1 -4 2 ... 0 0 0 1 0 2 */
- Generating a random number within the range
/*use range for a random number*/ Range r(-1, 100000); print("Random number in ", r, " is ", r.rand()); /*use range to generate several random numbers*/ auto [num1, num2, num3] = Range(1, 10).rand<3>();
- Filling a container with random numbers
/*use range to fill a C style array*/ double arr[10]; Range(-500.0, 500.0).fillRand(arr); /*use range to fill a STL container*/ std::array<char, 20> arr2; Range('A', 'z').fillRand(arr2); /*Alternatively .randFast() provides a faster way for generating random number using rand() in C*/ int arr3[10]; Range(-200, 300).fillRandFast(arr3);
-
Letter ranges
Similar functionality with numerical ranges, but works correctly when it is incremented it skips non letter characters
-
Container ranges(In progress)
SugarPP also has an Enumerate class, which returns a pair of index (default to start at 0) and a reference to the content of iterable, similar to Python's enumerate().
# Python a = ["enumerate", "in", "python"] for i, content in enumerate(a): print(i, content)
/*SugarPP*/ std::array a{"Enumerate", "in", "SugarPP"}; for(auto [i, content] : Enumerate(a)) print(i, content);
Usage
Just copy ./include/sugarpp/range/range.hpp and add #include "range.hpp" for Range.
Just copy ./include/sugarpp/range/enumerate.hpp and add #include "enumerate.hpp" for Enumerate.
More examples in ./test/source/range/range.cpp
Documentation
See docs/Range.md.
Type conversion
Features
- To & from string
-
-> number
Admit it,
atoi(),atof(),wcstold(),strtof()are some of the ugliest function name in C, and C++ makes it worse by adding more obsecure names likestd::stoi(),std::stolld(). That's why SugarPP provides a uniform way of getting numbers from string, which isSugarPP::to_num<Type>(), which accepts both normal strings and wide-strings./*SugarPP*/ auto str1 = "42"; auto num1 = to_num<int>(str1); auto str2 = "3.14159"; auto num2 = to_num<double>(str2);
-
-> string
Isn't it wired that something printable can't be converted to string? Isn't it wired that there is a
std::to_string()only works for numerical values?SugarPP::to_stringnot only works with anything that can be converted withstd::to_string(), but also anything that is printable. Additionally, you can also specify whether it's normal character or wide-character using one template argument.auto f_str = to_string(23.43); std::ostream& operator<<(std::ostream& os, const MyPrintableClass&); auto my_class_str = to_string(my_printable);
-
Usage
Just copy ./include/sugarpp/types/types.hpp and add #include "types.hpp".
More example in ./test/source/types/to_string.cpp
Lazy
Features
A C++ implementation for Kotlin's Lazy, which represents a value with lazy initialization,
with type inference from the initializer and multi-threading synchronization support.
Lazy lazyInt{ [] { return 1; } }; //Lazy<Int>
Lazy lazyDouble
{
[] {
/*Some computation*/
return 2.0;
},
ThreadSafetyMode::Synchronized
};Possible thread-safety modes are also equivalent to Kotlin
enum class ThreadSafetyMode { Synchronized, Publication, None };
Motivation
I had so much fun writing these and learned so much. Such a great language that gives you nightmare everytime you want to add stuff. C++ itself is difficult enough, yet you realize that you can't even have a compiler to trust with when 3 different compilers (Visual studio, Clang, GCC) gives you different results.