Giter Site home page Giter Site logo

cpputils's Introduction

CPPUTILS

CI@master

This is a repo in which I put stuff I think it could be usefull or interesting to some degree. It hasn't a precise goal or scope. For now it is a header-only library. Its components are

A non-promoting number-like class. It is a wrapper around the underlying number. In debug build, all operations are checked against under and overflow. In release build no checks are performed. All operations are non promoting. Bit operations are allowed only on unsigned types. All casts are explicit.

The class signature is

template<typename I>
class number;

Available aliases are

u8
u16
u32
u64
usize
i8
i16
i32
i64
isize
f32
f64

Literal (consteval) operators are defined inside the nested namespace cpputils::literals and can be used like so

1_u8
10_usize
etc...

Also a global True and False are available in the same namespace for convenience (these are simply number<bool>{true} and number<bool>{false} respectively).

std::numeric_limits<T> is specialized for all possible class templates.

The internal value is availbale via the val member function. Casts can be performed via static_cast or the as<T> member function.

using namespace cpputils::literals;
constexpr auto i = 11_u16;
assert(i.as<std::int16_t>() == 11);
assert(static_cast<std::uint16_t>(i) == 11);
assert(True);
assert(!False);

Range compatible stuff

zip takes an arbitrary values of containers (lvalues) and return a view of the zipped values. The shortest container is the limiting one. zip is a (lazy) range, and therefore support the transform syntax only as initial value (just like std::vector)

    using namespace cpputils;
    std::array arr{1, 2, 3};
    std::vector v{10, 20, 30, 40};
    int x[] = {4, 5, 6};
    for (auto [i, j, k] : zip(arr, v, x) {
        std::cout << i << "; " << j << "; " << << k '\n';
    }
    // Will print
    // 1; 10; 4
    // 2; 20; 5
    // 3; 30; 6
    // This is valid

    zip(arr, v, x)
        | std::ranges::transform([](int i) { return i * 2; });

zip_with is similar to zip but it first takes a callable to apply to each of the zipped elements.

    using namespace cpputils;
    auto const sum = [](int a, int b) { return a + b };
    std::array arr{1, 2, 3};
    std::vector v{10, 20, 30, 40};
    for (auto i : zip_with(sum, arr, v)) {
        std::cout << i << '\n';
    }
    // Will print
    // 11
    // 22
    // 33
    // This is valid
    zip_with(sum, arr, v)
        | std::ranges::transform([](int i) { return i * 2; });

Compatible with transform syntax. Custom types are supported as index (see example).

using namespace cpputils;
using namespace cpputils::literals;

int x[] = {10, 20, 30};
for (auto [index, value] : x | enumerate()) {
    std::cout << index << "; " << value << '\n';
}
// Will print
// 0; 10
// 1; 20
// 2; 30
for (auto [index, value] : x | enumerate(-1)) {
    std::cout << index << "; " << value << '\n';
}
// Will print
// -1; 10
// 0; 20
// 1; 30
for (auto [index, value] : x | enumerate(10_u32)) {
    std::cout << index << "; " << value << '\n';
}
// Will print
// 10; 10
// 11; 20
// 12; 30

auto const divide = [](int i) { return i / 2; };
// This is valid
x | std::ranges::views::transform(divide) | enumerate();

Converts a range to a std::vector.

using namespace cpputils;
assert(std::ranges::iota(5) | to_vector() == std::vector{0, 1, 2, 3, 4});

C++23 will add some monadic operations to optional, so probably some of these utilities will be less interesting in the future.

These functions can be used with any object which abides the optional_like concept (see src/include/cpputils/meta/traits.hpp):

template <typename T>
concept optional_like =
    requires (T opt) {
        { opt.value() } -> different_than<void>;
        { opt.has_value() } -> std::same_as<bool>;
        { opt.value_or(std::declval<std::remove_cvref_t<decltype(opt.value())>>()) }
          -> std::convertible_to<std::remove_cvref_t<decltype(opt.value())>>;
    }
    && explicitly_convertible_to<T, bool>;

map

Takes a callable and an arbitrary number of optionals. If all optionals have values call the callable with all the contained values, otherwise return an empty optional. The callable can return itself an optional or not.

using namespace cpputils;

std::optional a{1};
std::optional b{2};
map([](int i) { return i + 1 }, a); // == std::optional{2}
map([](int i, int j) { return i + j }, a, b); // == std::optional{3}
map([](int i, int j) { return std::optional{i + j} }, a, b); // == std::optional{3}
map([](int i, int j) { return std::optional{i + j} }, a, std::optional<int>{}); // == std::optional{}
map([](int i, int j) { return std::optional{i + j} }, std::optional<int>{}, std::optional<int>{}); // == std::optional{}

transform

Takes an optional and a callable and apply the callable to the optional's contained element, if any. Otherwise return an empty optional. The callable can itself return an optional (just like for map), the result is flattened. For a more readable syntax use the >> operator.

using namespace cpputils;

std::optional{1}
    >> transform([](int i) { return i + 1; })
    >> transform([](int i) { return i * 2; }); // == std::optional{4}

std::optional{1}
    >> transform([](int i) { return i + 1; })
    >> transform([](int i) { return std::optional{i * 2}; }); // == std::optional{4}

if_value/or_else

if_value takes and optional and a callable and apply the callable to the optional's contained element if any. Return a reference to the optional. or_else takes and optional and a callable and call the callable if the optional is empty. Return a reference to the optional.

using namespace cpputils;

auto const print = [](int i) { std::printf("%d\n", i); };
auto const panic = []() { throw std::invalid_argument{"Should not be empty"}; };

std::optional{}
    >> if_value(print)
    >> or_else(panic);

unwrap/unwrap_or/unwrap_or_else

unwrap calls value on the optional. unwrap_or calls value_or on the optional. unwrap_or_else calls value on the optional if it has a value, otherwise calls the provided fallback.

using namespace cpputils;

auto const get_good_default = []() { return 42; };

std::optional{1} >> unwrap(); // == 1
std::optional{} >> unwrap_or(10); // == 10
std::optional{} >> unwrap_or_else(get_good_default); // == 42

An exercise in template metaprogramming. I tried to mimic some of the most common functional features applied to types. The basic type is the typelist which is just en empty variadic struct.

The "functions" implemented are (in progress, for the full list see the test file)

Function Signature Description Usage 'Closure' form
inner inner :: typelist<T> -> T inner<typelist<T>>::type
head head :: typelist<T...> -> typelist<T> head<typelist<T...>>::list head<>::list<typelist<T...>>
tail tail :: typelist<T...> -> typelist<T> tail<typelist<T...>>::list tail<>::list<typelist<T...>>
join join :: typelist<T...> -> typelist<E...> -> ... -> typelist<T..., E..., ...> join<typelist<T...>, typelist<E...>>::list

Utility traits and concepts

is_specialization_of/is_specialization_of_v/an

To be used for template classes. Does not work for non-type template parameters. an is just the concept version of is_specialization_of.

using namespace cpputils;

static_assert(is_specialization_of_v<std::optional<int>, std::optional>);

auto f(an<std::optional> auto opt) { /* ... */ }

minimal_incrementable

A very relaxed incrementable requirement.

tl::specializable/tl::predicate/tl::transformation

Concepts for template metaprogramming. tl::predicate is satisfied for every type T such that exists T::value and it is convertible to bool. tl::transformation is satisfied for every type T such that typename T::type exists.

using namespace cpputils;

static_assert(tl::specializable<std::tuple, int, char>); // success
static_assert(tl::specializable<std::optional, int, char>); // fail

The symbol _ is a compile-time static object which allow to build function objects in a terse way. For example

using cpputils::_;

_ * _     // [](auto lhs, auto rhs) { return lhs * rhs; }
_ / 3     // [](auto const &lhs) { return lhs / 3; }
-_        // [](auto const &x) { return -x; }
0b010 & _ // [](auto const &x) { return 0b010 & x; }
_ > 100   // [](auto const &x) { return x > 100; }

And so on..

It works also with projections

using cpputils::_;

_.fn(&std::string::size) > 10U // [](std::string const &s) { return s.size() > 10U; }
_.fn(&std::string::size) == _ // [](std::string const &s, auto const &len) { return s.size() == len; }

It is possible to chain multiple method calls

using cpputils::_;

struct Obj {
    [[nodiscard]] int get() const { return v; }
    [[nodiscard]] Obj square() const {
        return Obj{v * v};
    }
    [[nodiscard]] int mult(int m) const {
        return v * m;
    }
    int v{42};
};

_.fn(&Obj::square).fn(&Obj::get) > _; // [](Obj const &obj, auto const &v) { return obj.square().get() > v; }
// Variadic packs of functions are supported
_.fn(&Obj::square, &Obj::get) > _; // [](Obj const &obj, auto const &v) { return obj.square().get() > v; }

If you don't know the type of the incoming value but know that it has a given member or member function you can either pass a generic lambda to fn or you can use one of three macros. Since macros are evil (reason #1, reason #2, reason #3, reason #4) in order to use them you must #define CPPUTILS_ENABLE_CALL_MACROS before the include.

#define CPPUTILS_ENABLE_CALL_MACROS
#include "cpputils/functional/operator_sections.hpp"

using cpputils::_;

_.fn(CALL(square)).fn(CALL(get)) > _; // [](auto const &obj, auto const &v) { return obj.square().get() > v; }

// If you need to pass arguments to the function pointer
auto const x = 10;
// By copy capture
_.fn(CALL_C(mult, x)) > 0; // [x](auto const &obj) { return obj.mult(x) > 0; }

// By reference capture
_.fn(CALL_R(mult, x)) > 0; // [&x](auto const &obj) { return obj.mult(x) > 0; }

For callable objects, args can be used.

using cpputils::_;

struct Callable {
    int x;

    int operator()(int y) const { return x + y; }
};

_.args(x) > _ // [x](auto const &obj, auto const v) { return obj(x) > v; }

A unified interface to produce a view of a container. Strings and char literals produce std::string_view, other containers produce std::span. If the size of the container is defined at compile-time, the span can be optionally produced with a proper Extent.

using namespace cpputils;

std::string const s{"Hey"};
auto const v = as_view(s); // -> std::string_view

const char *s = "Hey";
auto const v = as_view(s); // -> std::string_view

const wchar_t* s = L"ABCDEF";
auto const v = as_view(s); // -> std::basic_string_view<wchar_t>

// NOTE: containers of char-like type return a corresponding basic_string_view
char s[] = "ABCDEF";
auto const v = as_view(s); // -> std::string_view

std::vector<char8_t> s {'A', 'B', 'C',' D', 'E', 'F'};
auto const v = as_view(s); // -> std::basic_string_view<char8_t>
//

std::vector const vc{1, 2, 3};
auto const v = as_view(vc); // -> std::span<int const> const
//

auto const a = std::array{1, 2, 3};
auto const v = as_view(a); // -> std::span<int const> const

auto const a = std::array{1, 2, 3};
auto const v = as_view<with_fixed_extent>(a); // -> std::span<int const, 3> const

Two different implementations are provided.

Lambda implementation

A variable template that is also a lambda. The implementation is really lightweight but being the type of the lambda unique one cannot pass around such variables without using templates.

using namespace cpputils;

auto const lz = lazy<ComplexObject>(arg_0, arg_1, arg_2); // Nothing is created here, just a lambda with a captured variadic pack
auto const obj = lz(); // This is a ComplexObject{arg_0, arg_1, arg_2};

Class implementation

A more involved implementation that allow the use of custom builders (second template parameter).

Lazy<ComplexObject, ComplexObjectBuilder> const lz{arg_0, arg_1, arg_2}; // An object of type Lazy<_, _> is created togheter with a ComplexObjectBuilder.
auto const obj = lz(); // This is a ComplexObject{arg_0, arg_1, arg_2};

The requirements for a builder for an object of type TargetObject are:

  • Can be constructed with a lambda with signature [...]() -> TargetObject;
  • Has an operator()() -> TargetObject.

The default builder (used if the second template parameter is not specified) is std::function<TargetObject()>.

A custom builder could be for example:

template<typename TargetObject>
struct Builder {
    template <template B>
    explicit Builder(B const &b)
        : internal_builder{b}
        , builder{[](std::any const *bld) {
            return (*std::any_cast<B>(bld))(); 
        }} 
    {}

    TargetObject operator()() const { return builder(&internal_builder); }

    std::any internal_builder;
    TargetObject (*builder)(std::any const *);
};

Lazy can be than defined as:

using LazyComplexObject = Lazy<ComplexObject, Builder<ComplexObject>>;

Basically a fancy pointer to an object the exposes a very similar interface to std::optional. It abides the optional_like concept.

Assignment is equivalent to a rebind.

int x{10};
optional_ref opt{x}; // Store a poiter to x
int y{100};
opt = y; // opt now has a pointer to y
auto o_0 = opt.as_owned(); // o_0 is a std::optional<int>
auto o_1 = opt.take_ownership(); // o_1 is a std::optional<int> and y has been 'moved from'. This method moves the value pointed by the internal pointer.

Details

The tests are downloaded automatically in the build folder and are the only buildable thing. So doing make will build them. All typelist tests are compile-time checks, so if a test fail you get a compile-time error.

For now only gcc-11 on Ubuntu is used by the CI.

cpputils's People

Contributors

alessandro90 avatar

Watchers

James Cloos avatar  avatar

cpputils's Issues

Ranges are too limited

zip/zip_with and enumerate model only std::input_range regardless of the ranges they actually contain.
They should model the weakest range concept of the ranges they contain.

code coverage is wrong

The code coverage from ci.yml is wrong. On my local machine with the same gcovr command I get the expected results. But for some reason the github ci always show zero code coverage.

The command is

gcovr --root ../ --delete --exclude "_deps/*" --print-summary --xml-pretty --xml coverage.xml .

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.