com.github.benlau.underline

A C++ utility library provides useful functional programming helpers like lodash.js

version 0.0.1

To install:

❯ qpm install com.github.benlau.underline

Underline

Build Status Build status

Underline is a C++ utility library provides useful functional programming helpers like the lodash.js.

Features:

1) Designed for user-friendly error handling. Get rid of the horrific and misleading template compilation error messages.

Example:

    auto output = _::map(QList<QString>{"1","2","3"}, [](auto item, QString index) { return item;});

The 2nd argument of the iteratee function should be an int type instead of a QString. Usually, it triggers a compilation error at somewhere that user has no idea what is it doing. The error message is super long and hard to understand.

Underline captures the argument type mismatched error by using static_assert() in the early stage. The actual error message on above example is:

error: static_assert failed "_::map(): Mismatched argument types in the iteratee function. Please validate the number of argument and their type."

2) C++11 compliant coding. Support C++14 generic lambda function (using auto as parameter) and return type detection.

std::vector<int> output1 = _::map(std::vector<std::string>{"1","2","3"},
                              [](auto item) { return std::stoi(item);});

QList<int> output2 = _::map(QList<QString>{"1","2","3"},
                            [](auto item, auto index) {  return item.toInt() + index;});

QVector<int> output3 = _::map(QVector<QString>{"1","2","3"},
                              [](QString, int index, auto collection) { return collection[index].toInt();});

3) Support Qt types but it is still compilable even the Qt library is missing.

// Serialize a QObject
_::merge( /* QVariantMap */ dest, /* QObject* */ source );

// Obtain the objectName property from object's parent.
// It is equivalent to object->parent()->objectName() but _::get will take care null pointer checking
QVariant property = _::get(object, "parent.objectName");

4) Single Header Library

5) All the helper functions are reentrant, and thread-safe

Use-cases for regular C++

TODO

Use-cases for Qt

  1. Serialize a QObject
#include <underline.h>

  // Non-deep copy from source to dest
  _::assign( /* QVariantMap */ dest, /* QObject* */ source );

  // Serialize but ignore the parent object
  dest = _::omit(source, QStringList{"parent"});
  1. Avoid using method chaining to ask for a property value.
// Obtain the objectName property from object's parent.
// It is equivalent to object->parent()->objectName() but _::get will take care null pointer checking
QVariant property = _::get(object, "parent.objectName");

Installation

Direct Download

wget https://raw.githubusercontent.com/benlau/underline/master/src/cpp/underline.h

QPM

qpm install com.github.benlau.underline

API

assign

template <typename KeyValueType>
KeyValueType& _::assign(KeyValueType& object, const KeyValueType& source, ...);

This function assigns the properties from the source object to the destination object. The input should be an valid Key Value Type where _::isKeyValueType returns true. The sequence to apply source objects is from left to right. Subsequent sources overwrite property assignments of previous sources. It is a non-recursive function. For deep copying and merge, you should use _::merge.

Arguments:

  • object: The destination object
  • source && ... : The source object(s) to be assigned to the destination object. The key and value type of source object should be convertible to the destination object key-value system.

Returns:

  • The destination object

Example

_::assign(/*QObject*/ object, QVariantMap{{"objectName", "Test"}});
_::assign(map, object, QVariantMap{{"objectName", "Test"}});

clamp

template <typename Number>
Number _::clamp( number, Number lower, Number upper)

Clamps number within the inclusive lower and upper bounds.

Arguments:

  • number: The number to clamp.
  • lower: The lower bound.
  • upper: The upper bound.

Returns

  • The clamped number.

Example

    ASSERT_EQ(_::clamp(5,4,7), 5);
    ASSERT_EQ(_::clamp(-7,-4,7), -4);
    ASSERT_EQ(_::clamp(1,-20,-10), -10);

countBy

template <typename Array, typename Iteratee>
Map countBy(const Array& array, Iteratee iteratee)

Iterates all the elements in the array container and pass to the iteratee function, then count the number of the occurrence of the result. The iteratee function takes only one argument. [value]

The return is a Map object in a type either of std::map or QMap. The actual type chosen depends on the array container. If it is a Qt container class, it will be a QMap. Otherwise, it will be an std::map. The key type is the same as the return of the iteratee, and the value type is an integer storing the counting.

Arguments:

  • array: The input source. Check _::isArray function for the supported types
  • iteratee: The iteratee function to transform the element in the array to a key value.

Examples:

STL:

    class Student {
    public:
        std::string name;
        int age;
    };

    std::vector<Student> students = {{"Ben", 18} , {"Mary", 17}, {"John", 18}};

    std::map<int, int> stats = _::countBy(students, [=](const Student& student) { return student.age;});
    ASSERT_EQ(stats[18], 2);
    ASSERT_EQ(stats[17], 1);

Qt

    QVariantList students = {QVariantMap{{"name","Ben"},   {"age", 18}},
                             QVariantMap{{"name","Mary"},  {"age", 17}},
                             QVariantMap{{"name","John"},  {"age", 18}}};

    QMap<int,int> stats = _::countBy(students, [=](const QVariant& student) { return student.toMap()["age"].toInt();});
    ASSERT_EQ(stats[18], 2);
    ASSERT_EQ(stats[17], 1);

forEach

template <typename Array, typename Iteratee>
inline const Array& forEach(const Array& collection, Iteratee iteratee);

Iterates all the elements in the array container and calls the iteratee function. The iteratee function can take from one to three arguments [value [index [array]]]. If the iteratee function returns a false value, the looping stop immediately.

Arguments:

  • object: The source object. Check _::isArray function for the supported types.
  • iteratee: The function invoked per iteration.

Return:

  • object: The input object

Example

forIn

template <typename KeyValueType, typename Iteratee>
KeyValueType& _::forIn(const KeyValueType& object, Iteratee iteratee)

Iterates all the string key properties of an object and calls the iteratee function. The iteratee function is invoked with one to three arguments: [value, [key, [object]]].

Arguments:

  • object: The source object. Check _::isKeyValueType function for the supported types.
  • iteratee: The function invoked per iteration.

Returns:

  • object

Example

get

template <typename isQtMetable>
QVariant _::get(const isQtMetable& object, const QString &path,QVariant defaultValue = QVariant());

Obtain the value from the source object at the given path. If the path does not exist, it returns the default value.

Arguments

  • object: The source check. Check _::isQtMetable for the supported types
  • path (QString): The path of the property to get
  • defaultValue(QVariant): The default value to return if the path does not exist.

Return:

  • value: The value in the given path. If the path doesn't exist, it will return the defaultValue.

Example (Qt):

// Obtain the objectName property from object's parent
QVariant property = _::get(/* QObject* */ object, "parent.objectName");

map

template <typename Array, typename Iteratee>
_::map(Array array, Iteratee iteratee)

Iterate all the elements of the array container and call the iteratee function. It creates a new array obtaining the result. The iteratee is invoked with one to three arguments: [value, [index, [array]]].

Arguments:

  • array: The input array collection. Check _::isArray for the supported types.
  • iteratee: The function invoked per iteration.

Returns:

  • (Array) The new mapped array. The container type is the same as the input collection.

Example:

STD:

QList<int> output1 = _::map(QList<QString>(){"1","2","3"},  [](auto item) { return item.toInt();});

QVector<int> output2 = _::map(QVector<QString>() {"1","2","3"}, [](auto item, int index) { return item.toInt();});

QVector<int> output3 = _::map(std::vector<QString>(){"1","2","3"}, [](auto item, int index, auto collection) { return item.toInt();});

merge

template <typename QtMetable1, typename QtMetable2>
QtMetable1& merge(QtMetable1& object, QtMetable2& source)

Iterates all the string-keyed properties from the source object then copy to the destination object. If the property exists on the destination object, it will call itself recursively for merging.

Arguments:

  • object: The destination object. Check _::isQtMetable for the supported types.
  • source: The source object. Check _::isQtMetable for the supported types.

Return:

  • object: The destination object

omit

template <typename QtMetable>
QVariantMap omit(const QtMetable& source, const QStringList& paths);

template <typename QtMetable>
QVariantMap omit(const QtMetable& source, const QString& path);

Creates a new object which is a clone of the source, but the properties listed in the paths are omitted.

Arguments

  • source: The source object
  • paths: The property paths to omit

Returns

  • QVariantMap: Returns the new object.

pick

template <typename QtMetable>
QVariantMap _::pick(const QtMetable& object, QStringList &paths);

template <typename QtMetable>
QVariantMap _::pick(const QtMetable& object, QString path);

Creates a new object which is a clone of the source object but only the properties listed in the paths are picked.

Arguments

  • source: The source object
  • paths: The property paths to pick

Returns

  • QVariantMap: Returns the new object.

Example

set

template <typename QtMetable>
void _::set(QtMetable &object, const QString &path, const QVariant &value)

Set the property from the destination object on the given path. If the object is a QVariantMap and portion of path doesn't exist, it is created.

Arguments

  • source: The input object. Check _::isQtMetable for the supported types.
  • paths: The property paths to set
  • value: The property value to be set

Return

  • object: The destination object

Example:

    // Input: {"a":1,"b":2,"c":{"d":"3"}}
    QVariantMap object = {{"a", 1}, {"b", 2.0}, {"c", QVariantMap{{"d", "3"}}}};

    _::set(object, "b", 2.1);
    _::set(object, "c.e", "3.1");
    _::set(object, "f.g", "4.1");

    ASSERT_EQ(object["b"].toDouble() , 2.1);
    ASSERT_EQ(object["c"].toMap()["d"].toString() , QString("3"));
    ASSERT_EQ(object["c"].toMap()["e"].toString() , QString("3.1"));
    ASSERT_EQ(object["f"].toMap()["g"].toString() , QString("4.1"));

    // Result: {"a":1,"b":2.1,"c":{"d":"3","e":"3.1"},"f":{"g":"4.1"}}

range

reduce

some

bool _::some(Collection collection, Predicate predicate)

Check if predicate function returns a truth value for any element in the collection. The iteration stops once it got a truth value. The predicate function is invoked with one to three arguments: [value, [index, [collection]]].

Arguments

  • collection: The input collection
  • predicate: The predicate function to be invoked per iteration

Returns

  • bool: Returns true if any element passes to the predicate function, otherwise, it is false.

Type Checker

isArray

bool _::isArray(const T&)
bool _::isArray<T>()

It is a static type checker to validate is the input type classified as a valid Array class supported by _. You rarely need to use this function directly.

Example:

QCOMPARE(_::isArray(std::vector<int>{}),    true);
QCOMPARE(_::isArray(QVector<int>{ }),       true);
QCOMPARE(_::isArray(QList<int>{ }),         true);
QCOMPARE(_::isArray(QVariantList{ }),       true);
QCOMPARE(_::isArray(QString{ }),            true);

QCOMPARE(_::isArray(std::map<bool,int>{}),  false);
QCOMPARE(_::isArray(QMap<int,int>{}),       false);
QCOMPARE(_::isArray(10),                    false);

isKeyValueType

It is a static type checker to validate is the input type classified as a valid Key-Value type supported by _. You rarely need to use this function directly.

It is a kind of data structure contains key-value pairs with unique keys. std::map and QMap are the typical examples. In this library, Qt's data types like QObject*, QJSValue and Gadget object also classified as this kind of type. You may use them as an input to functions like forIn, assign, and merge etc.

ASSERT_EQ(_::isKeyValueType(std::map<int,int>{}),   true);

ASSERT_EQ(_::isKeyValueType(10),                    false);
ASSERT_EQ(_::isKeyValueType(std::vector<int>{}),    false);
QCOMPARE(_::isKeyValueType(QMap<int,int>{}),       true);
QCOMPARE(_::isKeyValueType(QVariantMap{}),         true);
QCOMPARE(_::isKeyValueType(new QObject(this)),     true);
QCOMPARE(_::isKeyValueType(QJSValue()),            true);

QCOMPARE(_::isKeyValueType(QVariantList{ }),       false);
QCOMPARE(_::isKeyValueType(QString{ }),            false);

isQtMetable

    template <typename T>
    bool isQtMetable();

    template <typename T>
    bool isQtMetable(const T&);

It is a static type checker to validate is the input type classified as Qt type with a metable interface supported by _. This kind of object supports a string-keyed properties system. A typical example is QObject* or arbitrary classes with Q_GADGET macro. QVariantMap and QJSValue are also classified as this kind of type. You may use them as an input to functions like forIn, assign, and merge etc.

You rarely need to use this function directly.

Examples:

class GadgetObject {
    Q_GADGET
    Q_PROPERTY(int value MEMBER value)
public:
    int value;
};

QCOMPARE(_::isQtMetable(GadgetObject()),        true);
QCOMPARE(_::isQtMetable(QVariantMap{}),         true);
QCOMPARE(_::isQtMetable(new QObject(this)),     true);
QCOMPARE(_::isQtMetable(QJSValue()),            true);

isMap

bool _::isMap(const T&)
bool _::isMap<T>()

It is a static type checker to validate is the input type classified as a valid Map container class for _. You rarely need to use this function directly.

Standard C++

ASSERT_EQ(_::isMap(std::map<int,int>{}),   true);

ASSERT_EQ(_::isMap(10),                    false);
ASSERT_EQ(_::isMap(std::vector<int>{}),    false);

Qt

QCOMPARE(_::isMap(QMap<int,int>{}),       true);
QCOMPARE(_::isMap(QVariantMap{}),         true);

QCOMPARE(_::isMap(QVector<int>{ }),       false);
QCOMPARE(_::isMap(QList<int>{ }),         false);
Author

Ben Lau

Info
View on Github

License: MIT

Installs
Today 1
This week 1
This month 1
This year 1
Total 1
Dependencies

None.

Versions