SugarPP
SugarPP copied to clipboard
C++ syntactic sugar collection
SugarPP: syntactic 🍬 for programming in C++
SugarPP is a collection of syntactic sugar for C++ code.
- SugarPP: syntactic 🍬 for programming in C++
- How to Use
- Requirements
- Features
- Kotlin-style
whensyntax- Usage
- Documentation
- Read More
- Simpler IO
- Features
- Usage
- Documentation
- Range
- Features
- Usage
- Documentation
- Type conversion
- Features
- Usage
- Lazy
- Features
- Kotlin-style
- Motivation
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 detail::when_impl; consider upgrading to GCC 10.2 or newer~~ Nope, it is compatible with GCC 9.2 now :)
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.~~