SugarPP is a collection of syntactic sugar for C++ code.
-
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.txt
to 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.txt
to 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.
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
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); }
)();
}
Just copy ./include/sugarpp/when/when.hpp and add #include "when.hpp"
in your project.
See docs/When.md
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
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!
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
,input
will 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::cout
has an overload for - Anything that is iterable (i.e. has a
.begin()
or can be called withstd::begin
) - Nested iterables (at any depth)
std::tuple
andstd::pair
bool
will be printed asTrue
orFalse
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
.
Just copy ./include/sugarpp/io/io.hp and add #include "io.hpp"
.
More examples in ./test/source/io/io.cpp.
See docs/IO.md.
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)
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);
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
See docs/Range.md.
- 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_string
not 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);
-
Just copy ./include/sugarpp/types/types.hpp and add #include "types.hpp"
.
More example in ./test/source/types/to_string.cpp
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
};
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.