Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
robertramey opened this issue Jan 30, 2016 · 12 comments
Open

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

robertramey opened this issue Jan 30, 2016 · 12 comments

Comments

@robertramey
Copy link

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

@pfultz2
Copy link
Owner

pfultz2 commented Jan 30, 2016

Suppose you have a trait defined like this:

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

Then the trait can be checked like this with TICK_REQUIRES:

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

There is also macros for other forms such as lambdas and classes defined here: Template constraints.

Furthermore, you can just check them directly using std::enable_if as well if you don't like the macros. So in your example, you could write:

template<class T>
typename std::enable_if<(
    is_incrementable<T>()
), int>::type
fun(const T & i) {  
    return ++i;
}

Note, when I use std::enable_if, I always put parenthesis around the boolean parameter. This is not for stylistic reason, but rather to avoid the most vexing parse in some situations. However, I find the macros easier and more readable.

@robertramey
Copy link
Author

On 1/30/16 7:16 AM, Paul Fultz II wrote:

Suppose you have a trait defined like this:

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

Then the trait can be checked like this with |TICK_REQUIRES|:

template<class T, TICK_REQUIRES(is_incrementable())>
void increment(T& x)
{
x++;
}
From the documentation - I understand that this will trap at compile
time if T does not have increment functions.
There is also macros for other forms such as lambdas and classes defined
here: Template constraints
http://pfultz2.github.io/Tick/doc/html/requires/.

Furthermore, you can just check them directly using |std::enable_if|
as well if you don't like the macros.

Which macro? The most obvious on TICK_REQUIRES traps at compile time.
Which permits a function
to be included/excluded from consideration for Overload depending on
it's requirements?

So in your example, you could write:

template
typename std::enable_if<(
is_incrementable()
),int>::type
fun(const T & i) {
return ++i;
}
OK - But I don't see how one could discern that from the documentation.

One very confusing (to me) aspect of the documentation are the names
chosen for the examples.
I'm pretty familiar with Boost Concepts Checking Library and have also
read the concepts lite papers.
Also there is the information in cpp reference. The general practice
seems to be:

EquallyComparable // equally comparable concept
is_equally_comparable // compile time testable trait

In your documentation and examples - you use is_equally_comparable for
the concept and I don't
see any corresponding trait. but it seems they're the same thing.

BCCL uses BOOST_CONCEPT_ASSERT((EquallyComparable)) to enforce a
requirement
and has no corresponding trait.

Concepts Lite uses EquallyComparable() to enforce a concept - I
think it uses the samething
as a trait since it's a constexpr returning a bool

Soooo maybe the documenation would be easier to understand and more in line
with the concepts-lite - (which for better or worse, going to become
standard) you might
tweak the naming to something like the following:

EquallyComparable() is either a constexpr bool or an integral_constant
implicitly convertible to bool

Then maybe the following would work

template
std::enable_if<
EquallyComparable(),
int

::type
fun(constT & i) {
...
}

and concept checking could be invoked with:

static_assert<EquallyComparable(), "type requirement violation");

I confess that the syntax template<typename T, > is confusing
to me. For
one thing, it's not clear what one should do if there are multiple
template parameters.
It seems to require either advanced knowledge of C++ and/or
investigation into the
implementation of your library. Neither of these are convenient to the
normal
user such as myself.

For all I know, all this might well work already. I'll try it later. To
me it would have
the advantage keeping macros out of my application and not require
anything new
to learn for the user.

The usability of constraint definition is less of concern as it's a much
less
common operation. I haven't looked into it too much.

Note, when I use |std::enable_if|, I always put parenthesis around
the boolean parameter. This is not for stylistic reason, but rather to
avoid the most vexing parse in some situations. However, I find the
macros easier and more readable.
water for my mill

-- Robert Ramey www.rrsd.com (805)569-3793

@pfultz2
Copy link
Owner

pfultz2 commented Jan 31, 2016

Which macro? The most obvious on TICK_REQUIRES traps at compile time. Which permits a function to be included/excluded from consideration for Overload depending on it's requirements?

Yes that is correct.

OK - But I don't see how one could discern that from the documentation.

The documentation needs to be updated to cleary show what gets defined by TICK_TRAIT or tick::models.

In your documentation and examples - you use is_equally_comparable for the concept and I don't see any corresponding trait.

is_equally_comparable is the trait. I never refer to the concept.

EquallyComparable() is either a constexpr bool or an integral_constant implicitly convertible to bool

In C++, traits to check concepts such as DefaultConstructible are named is_default_constructible. Secondly, concept bool and integral_constant are not interoperable. For example, requires DefaultConstructible<T>() works if it is a concept bool, however, if it is an integral_constant then an explicit cast to bool is necessary(ie requires bool{DefaultConstructible<T>()}).

static_assert<EquallyComparable(), "type requirement violation");

You can do that already, as well as use TICK_TRAIT_CHECK. However, that doesn't constrain the function, it just produces a helpful error. For example,
if we used static_asssert with the increment function:

template<class T>
void increment(T& x)
{
    static_assert(is_incrementable<T>(), "Type requirements failed");
    x++;
}

However, if the user would like to use increment to build another trait(perhaps they don't want to reverse engineer the type requirements):

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

This trait will always be true, because increment is unconstrained.

I confess that the syntax template<typename T, > is confusing to me. For one thing, it's not clear what one should do if there are multiple template parameters.

It should always be the last parameter. I will clarify that with the documentation. There is no problem with multiple parameters nor vardiac template parameters(if there were it would be documented). Secondly, type requirements are for template parameters, so it makes sense to define them with the template parameters.

It seems to require either advanced knowledge of C++ and/or investigation into the implementation of your library.

That is the purpose of the macro: to abstract the ugly enable_if implementation details so the user doesn't have to worry learning about that. Besides the fact that enable_if exposes too much implementation details, it can have suprises sometimes(such as the most vexing parse or the boolean is not template dependent), and portability problems(MSVC has lots of bugs with their constexpr support). The macro takes care of those issues, so the user doesn't have to worry about those problems.

There is a pattern that needs to be followed, but it is a pretty simple pattern for the user to follow. It probably needs to be documented better, though.

@robertramey
Copy link
Author

I'm still not getting this.

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

will trap at compiler time is T doesn't support the ++ operator. Right?

But what do I do if I DON'T want it to trap - I just want the the
overload to be considered only if the ++ operator is considered.
Normally I would use

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

So will this trap or not? What do you recommend I use if I want a
function to participate in the overload - but not trap?

Robert Ramey

@pfultz2
Copy link
Owner

pfultz2 commented Jan 31, 2016

But what do I do if I DON'T want it to trap - I just want the the
overload to be considered only if the ++ operator is considered.

I don't know what you mean by trap. The TICK_REQUIRES constrains the function, that is if the boolean expression is false then the function doesn't exists, and this is how requires works in concepts lite as well. Technically speaking, the library does this by causing substitution failure when the boolean expression is false(under the hood it uses enable_if plus some extra things to avoid surprises).

What do you recommend I use if I want a
function to participate in the overload - but not trap?

TICK_REQUIRES will work.

@robertramey
Copy link
Author

On 1/31/16 2:59 PM, Paul Fultz II wrote:

But what do I do if I DON'T want it to trap - I just want the the
overload to be considered only if the ++ operator is considered.

I don't know what you mean by trap. The |TICK_REQUIRES| constrains the
function, that is if the boolean expression is false then the function
doesn't exists, and this is how |requires| works in concepts lite as
well. Technically speaking, the library does this by causing
substitution failure when the boolean expression is false(under the
hood it uses |enable_if| plus some extra things to avoid surprises).

What do you recommend I use if I want a
function to participate in the overload - but not trap?

|TICK_REQUIRES| will work.

Sorry for seeming obstinate - I'm still not getting it. Lets compare
with concepts lite
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3701.pdf

template
requires Sortable()
void sort(Cont& cont)

Will invoke a error at compile time if the type Cont doesn't fulfill the
requirements of Sortable.
As I understand the documentation, the equivalent in TICK is:

template<
typename Cont,
TICK_REQUIRES(Sortable())

void sort(Cont & cont)

Right?

Now if we move down to section 2.2.1 Overloading of the same document I
find the example

template<Associative_container C>
Iterator_type find(C&& assoc, const Key_type& key)
{
return assoc.find(key);
}

So that the find function is called if and only if C fulfills the
requirements of Sortable.
It never invokes any kind of compile time error.
What is the equivalent TICK syntax for this case?

Robert Ramey
www.rrsd.com
(805)569-3793

@pfultz2
Copy link
Owner

pfultz2 commented Feb 1, 2016

Will invoke a error at compile time if the type Cont doesn't fulfill the requirements of Sortable. As I understand the documentation, the equivalent in TICK is:

Its important to understand that the compiler error comes from overload resolution failure. That is the compiler could not find an overload for the function that fulfills the requirement. That is why the error produced says error: no matching function for call to 'sort(list<int>&). The Tick library produces the same error as in the example it says error: no matching function for call to 'increment'.

Next, the compiler produces a note for each candidate function about why it failed. So in the concept-lite example is says note: template constraints not satisfied because and then it lists why the constrains are not satisfied. In the Tick library, it produces a similiar note and says note: candidate template ignored: disabled by 'enable_if' [with T = foo]. Although, clang could be improved to produce more detail messages like shown in the paper.

What is the equivalent TICK syntax for this case?

Well, first it is using the terse syntax. So it needs to be transformed to using the equivalent requires clause:

template<class C>
requires Associative_container<C>()
Iterator_type<C> find(C&& assoc, const Key_type<C>& key)
{
    return assoc.find(key);
}

Then from there we just rewrite it using TICK_REQUIRES:

template<class C, TICK_REQUIRES(Associative_container<C>())>
Iterator_type<C> find(C&& assoc, const Key_type<C>& key)
{
    return assoc.find(key);
}

This is almost equivalent. This is because the Tick library does not do subsuming of expressions. So in the example they give, if there was another overload for just ranges(in the paper they are relying on some new C++17 lookup rules):

template<class R, class T, TICK_REQUIRES(Range<R>())>
Iterator_type<R> find(R&& r, const T& key)
{
    return std::find(begin(r), end(r), key);
}

Then we will have ambiguous overloads for std::map because it is both a Range and an Associative_container. There are two solutions to this problem. The first is to use tag dispatching, which the Tick library will automatically build the tag hierarchy for you from the refinements:

template<class C>
Iterator_type<C> find_impl(C&& assoc, const Key_type<C>& key, tick::tag<tick::is_associative_container>)
{
    return assoc.find(key);
}

template<class R, class T>
Iterator_type<R> find_impl(R&& r, const T& key, tick::tag<tick::is_range>)
{
    using std::begin;
    using std::end;
    return std::find(begin(r), end(r), key);
}

template<class R, class T, TICK_REQUIRES(tick::is_range<R>())>
Iterator_type<R> find(R&& r, const T& key)
{
    return find_impl(r, key, tick::most_refined<tick::is_associative_container<R>>());
}

The second solution is to use fit::conditional:

FIT_STATIC_FUNCTION_LAMBDA(find) = fit::conditional(
    [](auto&& assoc, const auto& key, TICK_PARAM_REQUIRES(tick::trait<is_associative_container>(assoc)))
    {
        assoc.find(key);
    },
    [](auto&& r, const auto& key, TICK_PARAM_REQUIRES(tick::trait<is_range>(r)))
    {
        using std::begin;
        using std::end;
        return std::find(begin(r), end(r), key);
    }
);

It is a little more compact, but of course is another dependency.

@robertramey
Copy link
Author

On 1/31/16 4:00 PM, Paul Fultz II wrote:

Will invoke a error at compile time if the type Cont doesn't
fulfill the requirements of Sortable. As I understand the
documentation, the equivalent in TICK is:

Its important to understand that the compiler error comes from
overload resolution failure. That is the compiler could not find an
overload for the function that fulfills the requirement. That is why
the error produced says |error: no matching function for call to
'sort(list&)|. The Tick library produces the same error as in the
example it says |error: no matching function for call to 'increment'|.

Next, the compiler produces a note for each candidate function about
why it failed. So in the concept-lite example is says |note: template
constraints not satisfied because| and then it lists why the
constrains are not satisfied. In the Tick library, it produces a
similiar note and says |note: candidate template ignored: disabled by
'enable_if' [with T = foo]|. Although, clang could be improved to
produce more detail messages like shown in the paper.

Right - it invokes a compile error and ignore the overload - not that
that matters any more since the program fails to compile.

What is the equivalent TICK syntax for this case?

template<class C, TICK_REQUIRES(Associative_container())>
Iterator_typefind(C&& assoc,const Key_type& key)
{
return assoc.find(key);
}

This is almost equivalent.

No it's not.

If C doesn't not fulfill the requirements of an Associative_container,
one will get a compile time error.
This is not what I want. I want the behavior specified by section 2.2.1
Overloading of the
document http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3701.pdf
which says the overload is excluded from consideration in this case.
There is not
compile time error.

What am I missing here?

Robert Ramey
www.rrsd.com
(805)569-3793

@pfultz2
Copy link
Owner

pfultz2 commented Feb 1, 2016

If C doesn't not fulfill the requirements of an Associative_container, one will get a compile time error.

No, you get overload resolution failure, which turns into an error if it can't find another overload.

which says the overload is excluded from consideration in this case.

The overload is excluded. The problem with the example is there can be two overloads considered for std::map. Concepts lite can resolve this using subsuming, however, there is no way to emulate this currently with C++, so it requires using either tag dispatching or conditional overloading to resolve the ambiguity.

Does that make sense?

@robertramey
Copy link
Author

On 1/31/16 4:41 PM, Paul Fultz II wrote:

If C doesn't not fulfill the requirements of an
Associative_container, one will get a compile time error.

No, you get overload resolution failure, which turns into an error if
it can't find another overload.

which says the overload is excluded from consideration in this case.

The overload is excluded. The problem with the example is there can
be two overloads considered for |std::map|. Concepts lite can resolve
this using subsuming, however, there is no way to emulate this
currently with C++, so it requires using either tag dispatching or
conditional overloading to resolve the ambiguity.

Does that make sense?

It's starting to. I think the problem is that there is a difference
between what the program does and what I expected it to do.

Going back to the original example

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

Then the trait can be checked like this with |TICK_REQUIRES|:

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

If T doesn't support the ++ operator we get a compilation error with a
useful error message saying that the ++ operator is not supported.
This is what one would expect if he is familiar
with concepts lite. So far so good.

But suppose I don't want the overload to be considered. If the
function call is not resolved any more I want a different
error message - saying it couldn't find a matching function.

I believe that this is what the concepts lite proposal assumes
which is why it has a different syntax.

But I'm beginning to see that you might not agree these are
distinguishable cases. But then that raises questions about
the concepts lite proposal itself. For the moment I'll leave
this aside.

Another confusion is between type requirements (aka concepts)
and (function or type) traits. I agree that requirements/concepts
should invoke a compile time error. But what I want to do is
detect them at compile time without necessarily generating
an error. something like

boost::mpl::if_<
is_incrementable<T, return one type, return a different type>

It's not clear whether or not one can do this with TICK_TRAITS. Put
another way - is the compile error invoked by code hidden TICK_REQUIRES or
by the code which is hidden in TICK_TRAIT. Of course I can
dig through the code and figure it out - probably by experiment.
But I would hope a plain reading of the documentation should
reveal that. As an aside, this is a downside if using the macros.

I'm not trying to make your life difficult. I see you've invested
a lot of work here. I like parts of this alot and see a real
need for it. I've been a strong proponent of using type
requirements in libraries and associated documentation. I haven't
be an enthusiastic fan of concepts lite though. I believe that
a library solution such as yours is easier to implement, understand
and support. Having said that, I know that concepts lite is
coming down the pike so this might be a good way to prepare
people for it.

I would also like the documentation to do more to "sell" type
requirements - similar to the effort I made in the boost library
incubator pages.

A side issue - I think DOxygen isn't very good a producing
helpful documentation.

Robert Ramey

-- Robert Ramey www.rrsd.com (805)569-3793

@pfultz2
Copy link
Owner

pfultz2 commented Feb 1, 2016

If T doesn't support the ++ operator we get a compilation error with a useful error message saying that the ++ operator is not supported.

Thats not the error message you get. Rather the error message is no matching call for function with a note pointing to the type requirements of increment.

But suppose I don't want the overload to be considered. If the function call is not resolved any more I want a different error message - saying it couldn't find a matching function.

And thats the error you do get with TICK_REQUIRES.

But I'm beginning to see that you might not agree these are distinguishable cases.

I am not sure I fully understand your different cases. Are you talking about the difference between using requires/enable_if vs static_assert?

But what I want to do is detect them at compile time without necessarily generating an error.

Well these are boolean type traits, just like the type traits provided by the standard library or boost. They don't work anything like Boost.ConceptCheck library at all.

It's not clear whether or not one can do this with TICK_TRAITS. Put another way - is the compile error invoked by code hidden TICK_REQUIRES or by the code which is hidden in TICK_TRAIT.

There is not that much magic going on here(as compared to Boost.ConceptCheck). The TICK_TRAIT macro creates a boolean type trait that can be used anywhere to check true or false if a type meets the type requirements. You can use it in static_assert, enable_if, TICK_REQUIRES and even standard if statement.

Then the TICK_REQUIRES macro takes a boolean expression that will constrain the function according to that boolean expression. You can use C++ type traits such as TICK_REQUIRES(std::is_integral<T>()) or boolean operators such as TICK_REQUIRES(std::is_integral<T>() && is_incrementable<T>()) or even literal boolean values like TICK_REQUIRES(false).

But I would hope a plain reading of the documentation should reveal that.

Yes the documentation needs to expand upon these things. Although it states these things, it perhaps is not the clearest for people who have used Boost.ConceptCheck which is very different from this library. I can see now how this can cause confusion. I think it will be a good idea if I write up an introduction for those coming from Boost.ConceptCheck.

I'm not trying to make your life difficult.

The feedback is helpful, especially as I work toward improving the documentation.

I would also like the documentation to do more to "sell" type requirements - similar to the effort I made in the boost library incubator pages.

There is a lot of work that needs to be done for the documentation. In fact, not everything has even been documented yet. All the traits in the tick/traits directory still need documentation. And that is a large effort in itself. Of course, if you would like to lend a hand in writing documentation, it would be greatly appreciated, especially if you are passionate about "selling" it.

A side issue - I think DOxygen isn't very good a producing helpful documentation.

I don't use Doxygen anyways.

@robertramey
Copy link
Author

robertramey commented Sep 3, 2017

OK - I'm back. Now I want to make a new trait. Looking at section "Getting Started" there is

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

I don't see anywhere in the documentation where "require" or "valid" are described. It would seem that it's not possible for a naive user to discern how to create his own trait from looking at the documentation alone. I can probably try to analyze the code in the "build.h" header, but I don't think that's what you expect users to do.

Also, the section Design Notes might benefit from a section - how the library is implemented.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants