Design Notes

Using template class

Tick uses a the valid template class to place valid expressions, because it provides a more robust solution. Ideally, using tick we could define the traits like this:

TICK_TRAIT(is_incrementable)
{
    template<class T>
    auto require(T&& x) -> decltype(
        x++,
        ++x
    );
};

However, if one of the expressions returns a type that overloads the comma operator in a strange way(rare but still possible), then the trait could fail(ie return false when it should be true). To fix it, we could add void casts like this:

TICK_TRAIT(is_incrementable)
{
    template<class T>
    auto require(T&& x) -> decltype(
        (void)x++,
        (void)++x
    );
};

However, the void casts can be easy to forget. Another solution to the problem could be to pass it to a function:

template<class... Ts>
void valid_expr(T&&...);

TICK_TRAIT(is_incrementable)
{
    template<class T>
    auto require(T&& x) -> decltype(valid_expr(
        x++,
        ++x
    ));
};

However, if one of the expressions returns void, then this will fail as well(ie return false when it should be true). So this could be fixed in a similiar way as well:

TICK_TRAIT(is_incrementable)
{
    template<class T>
    auto require(T&& x) -> decltype(valid_expr(
        (x++, 1),
        (++x, 1)
    ));
};

However, it can be easy to forget to put the 1 in there. So instead we use a valid template class, like this:

TICK_TRAIT(is_incrementable)
{
    template<class T>
    auto require(T&& x) -> valid<
        decltype(x++),
        decltype(++x)
    >;
};

This requires placing each expression in a decltype, but if this was forgotten there would be a compile error pointing to the incorrect expression.

Trait-based

The concept predicates in Tick are defined as regular type traits(ie they are integrel constants) instead of a constexpr bool function. They are almost functionally the same in use. However, as a trait, it allows for better flexibility and expressiveness, through higher-order programming. This is what enables passing the traits to other functions which can be used to match return types to traits as well as other types.

Specializations

All the traits created can be specialized by the user. This is very important. Since the definition of traits relies on duck typing, there are times that even though it may quack like a duck it is not a duck. So with specialization the user can clarify the type's capabilities.