Git Product home page Git Product logo

tick's Introduction

Tick

Trait introspection and concept creator for C++11

Getting Started

Tick provides a mechanism for easily defining and using traits in C++11. For example, if we defined a generic increment function, like this:

template<class T>
void increment(T& x)
{
    x++;
}

If we pass something that does not have the ++ operator to increment, we will get an error inside of the increment function. This can make it unclear whether the error is due to a mistake by the user of the function or by the implementor of the function. Instead we want to check the type requirements of the function.

Using Tick we can create an is_incrementable trait, like this:

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

And then we can use a simple requires clause in our function to check the type requirements:

template<class T, TICK_REQUIRES(is_incrementable<T>())>
void increment(T& x)
{
    x++;
}

So, now, if we pass something that is not incrementable to increment:

struct foo {};

foo f;
increment(f);

Then we get an error like this in clang:

demo.cpp:25:2: error: no matching function for call to 'increment'
        increment(f);
        ^~~~~~~~~
demo.cpp:14:19: note: candidate template ignored: disabled by 'enable_if' [with T = foo]
template<class T, TICK_REQUIRES(is_incrementable<T>())>
                  ^

This gives an error at the call to increment rather than inside the function, and then pointes to the type requirements of the function. This gives enough information for most commons cases, however, sometimes we may want more information. In that case the TICK_TRAIT_CHECK can be used. For example, say we had the is_incrementable trait defined like this:

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

Then if we use TICK_TRAIT_CHECK, we can see why int* is not incrementable:

TICK_TRAIT_CHECK(is_incrementable<int*>);

Which will produce this error:

../tick/trait_check.h:95:38: error: implicit instantiation of undefined template 'tick::TRAIT_CHECK_FAILURE<std::is_integral<int *>, is_incrementable<int *> >'

Which shows the traits that failed including any refinements. So we can see that it failed because std::is_integral<int *> is not true.

Building traits

This macro will build a boolean type trait for you. Each trait has a require member function of the form:

TICK_TRAIT(my_trait)
{
    template<class T>
    auto require(T&& x) -> valid<
        ...
    >;
};

This will essentially build a class that inherits from integral_constant, so the above is equivalent to this:

template<class... Ts>
struct my_trait
: integral_constant<bool, (...)>
{};

The parameters to the trait are based on the parameters passed to the require function.

The trait will be either true or false if the expressions given are valid. Each expression in valid needs a decltype around it. If one of the expressions is not valid, the the trait will return false. For example,

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

The trait above will check that x++ is a valid expression.

Refinements

Refinements can be expressed after the name. Each refinement is a placeholder expression, where each placeholder(ie _1, _2, etc) are replaced by their corresponding type passed into the trait. In the case of traits that accept a single parameter the unnamed placeholder(_) can be used, for example:

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

This trait will be true when x++ and ++x are valid expressions and x is default constructible.

When a trait has multiple parameters, its best to use named placeholders. For example:

TICK_TRAIT(is_equality_comparable, 
    std::is_default_constructible<_1>, 
    std::is_default_constructible<_2>)
{
    template<class T, class U>
    auto require(T&& x, U&& y) -> valid<
        decltype(x == y),
        decltype(x != y)
    >;
};

This trait will be true when x == y and x != y are valid expressions and both x and y are default constructible.

In addition quote can be used to pass all the args from the trait to the refinement:

TICK_TRAIT(is_comparable, 
    quote<is_equality_comparable>)
{
    template<class T, class U>
    auto require(T&& x, U&& y) -> valid<
        decltype(x < y),
        decltype(x <= y),
        decltype(x >= y),
        decltype(x > y)
    >;
};

Query operations

These can be used to query more information about the types then just valid expressions.

Type matching

When a type is matched, it can either be convertible to the type given, or the evaluated placeholder expression must be true. Placeholder expressions can be given so the type can be matched against other traits.

returns

The returns query can check if the result of the expressions matches the type. For example,

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

This trait will be true if the expressions x++ is valid and is convertible to int.

Here's an example using placeholder expressions as well:

TICK_TRAIT(is_incrementable)
{
    template<class T>
    auto require(T&& x) -> valid<
        decltype(returns<std::is_integral<_>>(x++))
    >;
};

This trait will be true if the expressions x++ is valid and returns a type that is_integral.

Note: The TICK_RETURNS macro can be used instead to improve compatability with older compilers(such as gcc 4.6):

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

Also, returns<void> is prohibited.

TICK_TRAIT(is_incrementable)
{
    template<class T>
    auto require(T&& x) -> valid<
        decltype(returns<void>(x++)) // Compiler error
    >;
};

Instead, use either decltype directly without returns, or if there is a possibility of void from a computed type, use TICK_RETURNS or has_type instead.

has_type

The has_type query can check if a type exist and if the type matches. For example:

TICK_TRAIT(has_nested_type)
{
    template<class T>
    auto require(const T& x) -> valid<
        has_type<typename T::type>
    >;
};

This trait will be true if T has a nested type called type.

Now has_type used as above is not quite as useful since the above example, can also be simply written without has_type like this:

TICK_TRAIT(has_nested_type)
{
    template<class T>
    auto require(const T& x) -> valid<
        typename T::type
    >;
};

So, an optional second parameter can be provided to check if the type matches. Here's an example:

TICK_TRAIT(has_nested_int_type)
{
    template<class T>
    auto require(const T& x) -> valid<
        has_type<typename T::type, std::is_integral<_>>
    >;
};

This trait will be true if T has a nested type called type which is an integral type.

has_template

The has_template query can check if a template exist. For example:

TICK_TRAIT(has_nested_result)
{
    template<class T>
    auto require(const T& x) -> valid<
        has_template<T::template result>
    >;
};

This trait will be true if T has a nested template called result.

Trait evaluation

The is_true and is_false queries can check if a trait is true or false. Using refinements is the preferred way of checking for additional traits, but this can be useful if the evaluation of some trait can't be used lazily with placeholder expressions. So the is_true and is_false can be used instead, for example:

TICK_TRAIT(is_2d_array)
{
    template<class T>
    auto require(const T& x) -> valid<
        is_true<std::is_same<std::rank<T>::type, std::integral_constant<std::size_t, 2>> >
    >;
};

Helper functions

The library also provides as_const and as_mutable functions to ensure lvalues are either const or mutable respectively:

TICK_TRAIT(is_copy_assignable)
{
    template<class T>
    auto require(T&& x) -> valid<
        decltype(x = as_const(x))
    >;
};

Build traits without macros

The traits can be built without using the TICK_TRAIT macros. However, it may introduce problems with portability. So if only one platform is needed to be supported, then here's how to build them. First, build a class for the require functions and inherit from tick::ops to bring in all the query operations:

struct is_incrementable_r : tick::ops
{
    template<class T>
    auto require(T&& x) -> valid<
        decltype(x++),
        decltype(++x)
    >;
};

Next, turn it into a trait using tick::models:

template<class... Ts>
struct is_incrementable
: tick::models<is_incrementable_r, Ts...>
{};

Refinements

Refinements can be used by using the tick::refines class:

struct is_incrementable_r 
: tick::ops, tick::refines<std::is_default_constructible<tick::_>>
{
    template<class T>
    auto require(T&& x) -> valid<
        decltype(x++),
        decltype(++x)
    >;
};

Notice, the placeholders have to be fully qualified here.

Template constraints

Three macros are provided to help improve the readability of template constraints.

TICK_REQUIRES

The TICK_REQUIRES can be used on template parameters. For example,

template<class T, TICK_REQUIRES(is_incrementable<T>())>
void increment(T& x)
{
    x++;
}

TICK_CLASS_REQUIRES

The TICK_CLASS_REQUIRES can be used when template specialization is done on classes. For example,

template<class T, class=void>
struct foo
{
    ...
};

template<class T>
struct foo<T, TICK_CLASS_REQUIRES(is_incrementable<T>() and not std::is_integral<T>())>
{
    ...
};

template<class T>
struct foo<T, TICK_CLASS_REQUIRES(std::is_integral<T>())>
{
    ...
};

TICK_MEMBER_REQUIRES

The TICK_MEMBER_REQUIRES can be used for member function inside of classes, that are not templated. For example,

template<class T>
struct foo
{
    T x;

    TICK_MEMBER_REQUIRES(is_incrementable<T>())
    void up()
    {
        x++;
    }
};

TICK_PARAM_REQUIRES

The TICK_PARAM_REQUIRES can be used in the paramater of the function. This is useful for lambdas:

auto increment = [](auto& x, TICK_PARAM_REQUIRES(is_incrementable<decltype(x)>()))
{
    x++;
};

Also, the trait function is provided which can be used to deduce the type of the parameters:

auto increment = [](auto& x, TICK_PARAM_REQUIRES(trait<is_incrementable>(x)))
{
    x++;
};

Note: The trait function always deduces the type without references. So trait<std::is_lvalue_reference>(x) will always be false.

TICK_FUNCTION_REQUIRES

The TICK_FUNCTION_REQUIRES can be used on functions. This requires placing parenthesis around the return type:

template<class T>
TICK_FUNCTION_REQUIRES(is_incrementable<T>())
(void) increment(T& x)
{
    x++;
}

Note: The TICK_REQUIRES should be preferred.

Trait checking

The TICK_TRAIT_CHECK macro will statically assert the list of traits that are true but it will show what traits failed including base traits. This can be useful to show more informative messages about why a trait is false.

Requirements

This requires a C++11 compiler. There a no third-party dependencies. This has been tested on clang 3.4, gcc 4.6-4.9, and Visual Studio 2015.

ZLang support

ZLang is supported for some of the macros. The macros are in the tick namespace. For example,

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

Acknowledgments

tick's People

Contributors

akrzemi1 avatar kyucrane avatar manu343726 avatar pfultz2 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tick's Issues

Can I require that an expression be noexcept?

Is there a way to express directly in the framework that a given expression must be noexcept?
Currently, I am using my custom hack:

  template<class T>
  auto require(T&& x) -> valid<
    std::enable_if_t<noexcept(x.f())>
  >;

But maybe there is a better way?

Logical ops for trait evaluation and property checking

is_true and is_false work nice, but I find myself doing n-ary std::integral_constant<bool, A::value || B::value || ...> many times, so I will be glad if there's some support for or operations out of the box. The usual n-ary or_ metafunction should do the trick.

By default valid is an and since it's instanced successfully only if all its parameters are valid. In many cases, a concept requirement is not a set of mandatory properties in a way that all of them should be accomplished by the type, but a set of possible properties.

`require` with non-template arguments breaks

The following program works fine (on GCC 7.2):

#include <tick/builder.h>
  
struct has_f2_r : tick::ops
{
  template<class T>
  auto require(T&& x) -> valid<
    decltype(x.f(1, 2)) // <- using literals 1 and 2
  >;
};
  
template<class T>
struct has_f2 : tick::models<has_f2_r, T>
{};

struct A { void f(int, int) {} };
struct B { void f(int) {} };

int main()
{
    static_assert(!has_f2<B>::value, "** 1");
    static_assert(has_f2<A>::value, "** 2");
}

But I do not like it, because I am using literals 1 and 2 rather than saying "any i, j of type int. So I change function require to:

  template<class T>
  auto require(T&& x, int i, int j) -> valid<
    decltype(x.f(i, j))
  >;

And now my concept stops working: it will always return false. The entire program:

#include <tick/builder.h>
  
struct has_f2_r : tick::ops
{
  template<class T>
  auto require(T&& x, int i, int j) -> valid<
    decltype(x.f(i, j))
  >;
};
  
template<class T>
struct has_f2 : tick::models<has_f2_r, T>
{};

struct A { void f(int, int) {} };
struct B { void f(int) {} };

int main()
{
    static_assert(!has_f2<B>::value, "** 1");
    static_assert(has_f2<A>::value, "** 2"); // <- fires!
}

However, when I provide my own simplified implementation of models based on yours, it works again:

#include <type_traits>
#include <tick/builder.h>
  
struct has_f2_r : tick::ops
{
  template<class T>
  auto require(T&& x, int i, int j) -> valid<
    decltype(x.f(i, j))
  >;
};

template <typename... Ts>
using void_t = void;

template <typename _, typename C, typename... Args>
struct models_ : std::false_type {};
 
template <typename C, typename... Args>
struct models_<void_t<decltype(&C::template require<Args...>)>, C, Args...>
  : std::true_type {};
 
template <typename C, typename... Args>
  using models = models_<void, C, Args...>;
  
template<class T>
struct has_f2
: models<has_f2_r, T>
{};

struct A { void f(int, int) {} };
struct B { void f(int) {} };

int main()
{
  static_assert(!has_f2<B>::value, "** 1");
  static_assert(has_f2<A>::value, "** 2");
}

So, I wonder, if this can be fixed in Tick?

how does one check to see whether a trait is true or false?

I'm looking this over to address a problem I have and I have a question. Suppose I've define the is_incrementable as you have in your first example. What I would like to do is something like

template
std::enable_if<
is_incrementable::value // syntax for this?
int

fun(const T & i){
return ++i;
}

I'm expecting this is possible but I don't see it in the documentation. Please explain this to me.

Robert Ramey

TICK_REQUIRES with parameter pack template

c++ prefers paramter packs as last arguement

template<class... T_MyPack, TICK_REQUIRES(MyPackConstriants<T_MyPack...>())>
class SomeClass
{

}

How do i declare class that takes pack, and uses TICK_REQUIRES in class header to help documnnet constraints used closer to class header.

Currently had to resolve using static_assert(MyPackConstriants<>() ) inside the class.

So again the problem generally is doing TICK with variable argument templates

cheers

How to Declare Tick class as friend

Hi

I want my tick trait class to have private access to its argument T type.

So i need to declare my tick triat class as friend of T.

How do i do that , cause not sure what the ecat class prototype would be for tick class cause i use the Macro

example
`TICK_TRAIT(My_Trait)
{
    template<class T_SomeType>
    auto require(T&& x) -> valid<
        decltype(T_SomeType::SomeFunc()) 
    >;
};`
class SomeClass{

private:
friend My_Trait<SomeClass>;
SomeFunc(){}
}

In summary need to declare Tick class as friend of SomeClass
Thanks

Any license?

Is there any license tat applies to this library? It is listed in Boost Library Incubator. Do you intend to distribute it under Boost Software License 1.0?

concept primitives?

Have you thought of including some basic primitive concepts? like those defined in "Elements of Programming" and similar to what will be in concepts-lite

How to disable C<T>.f(T) for T void?

In a template class with template parameter T, I'd like to conditionally enable a non-template method taking a parameter of type T - enable if T is not void:

template <typename T>
class C {
public:
  void f(T t) { }    // enable for non-void T
  void f() { }       // enable for void T
};

int main() {
  C<int>().f(1);
  C<void>().f();
  return 0;
}

Compiling the above with g++ -std=c++17 -Wall gives
In instantiation of ‘class C<void>’: invalid parameter type ‘void’

I tried TICK_MEMBER_REQUIRES, but I'm getting the same error

#include <type_traits>
#include "tick/requires.h"

template <typename T>
class C {
public:
  TICK_MEMBER_REQUIRES(!std::is_void_v<T>)
  void f(T t) { }

  TICK_MEMBER_REQUIRES(std::is_void_v<T>)
  void f() { }
};

int main() {
  C<int>().f(1);
  C<void>().f();
  return 0;
}

Is there any way I could disable the first f overload for T = void without changing f parameter type from T to some dummy template parameter type T_, as below?

template <typename T>
class C {
public:
  template <typename T_ = T, typename = std::enable_if_t<!std::is_void_v<T_>>>
  void f(T_ t) {}

  template <typename T_ = T, typename = std::enable_if_t<std::is_void_v<T_>>>
  void f() {}
};

Thanks for help.

Use variable templates to avoid unwanted parse?

For c++14 and above, have you thought of defining the traits as variable templates instead of structs? It would avoid an undesirable parse when using them with non-type template parameters. Below's an example:

#include <iostream>                                                                
#include <tick/builder.h>                                                          

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

struct A {};                                                                       

template<bool>                                                                     
struct B {                                                                         
  static const int value = 1;                                                      
};                                                                                 

template<>                                                                         
struct B<false> {                                                                  
  static const int value = 2;                                                      
};                                                                                 

template<class... T>                                                               
constexpr is_incrementable<T...> is_incrementable2 = is_incrementable<T...>();     

int main() {                                                                       
  // these won't work; they're parsed as function types        
  // I could use something like static_cast<bool>(is_incrementable<int>()), but yuck!                    
  //std::cout << B<is_incrementable<int>()>::value << "\n";                        
  //std::cout << B<is_incrementable<A>()>::value << "\n";                          

  std::cout << B<is_incrementable2<int>()>::value << "\n";                         
  std::cout << B<is_incrementable2<A>()>::value << "\n";                           
  return 0;                                                                        
}     

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.