Repository: ericniebler/range-v3

Issue #94: Faster root cause identification in concepts violation
Created: 2015-01-05T12:03:36Z
When a class violates a concept, it should be as quick and clear as possible to pin down the root cause of the violation. Is there currently such a way to do it?
Example scenario:
I have a custom class DataFrame with a custom iterator DataFrameIterator. I had forgotten to mark operator* of DataFrameIterator as const.
I called ranges::for_each and got the error no matching function for call to object of type ‘const for_each_fn’.
My lengthy way to find out the root cause (the non-const operator*) is described below. I describe it just to show that it was quite lengthy for a simple problem. Is there some better current way of going about finding the root cause of such issues? If not, that would be high on the list of making ranges more user friendly for anyone using custom datatypes.
For example, a models_assert<InputIterable>(df) or even better models_assert<for_each_fn::requirements>(df) which would give a compile time error with relevant information (as close as possible to the root cause) would be excellent. In concepts.hpp:116 models_ uses some SFINAE and metaprogramming to give true_type if requires_ compiles and it compiles for all recursively refined types. Using similar metaprogramming, but not SFINAE, it should be straight forward (?) to make such a models_assert. Understanding enough of the metaprogramming framework to do that is a bit outside my current time schedule though :-(.
Is there any ongoing work in this direction?
The root cause identification
I started by manually extracting the concept checking pieces of for_each_fn::operator():
typename P = ident,
typename I = range_iterator_t<Rng>,
typename V = iterator_common_reference_t<I>,
typename X = concepts::Invokable::result_t<P, V>,
CONCEPT_REQUIRES_(InputIterable<Rng &>() && Invokable<P, V>() && Invokable<F, X>())

Into equivalent usings and asserts at the call sites. It became something like:
using P = ident;
using I = range_iterator_t<Rng>;
using V = iterator_common_reference_t<I>;
using X = concepts::Invokable::result_t<P, V>;
static_assert(InputIterable<Rng &>(),””);
static_assert(Invokable<P, V>(),””);
static_assert(Invokable<F, X>(),””);

Here static_assert(InputIterable<Rng &>(),””); failed.
I checked the definition of InputIterable and saw that it refined Iterable and had a requires_ method which added that concepts::model_of<InputIterator>(begin(t)).
A static_assert(Iterable <Rng &>(),””); succeeded and thus the problem narrowed down to that DataFrameIteratordid not satisfy InputIterator.
I continued in the same fashion.
static_assert(InputIterator<ty>(),””); //fails

//InputIterator refines WeakInputIterator, Iterator and EqualityComparable
static_assert(WeakInputIterator<ty>(),””); //fails
static_assert(Iterator<ty>(),””); //ok
static_assert(EqualityComparable<ty>(),””); //ok

//WeakInputIterator refines WeakIterator and Readable
static_assert(WeakIterator<ty>(),””); //ok
static_assert(Readable<ty>(),””); //fails

//Readable refines SemiRegular
static_assert(SemiRegular<ty>(),””); //ok

Thus one root cause was narrows down to the specifics of Readable (as coded in its requires_ method). Others might possibly exist in the specifics of InputIteratorand WeakInputIterator.
Readable::requires_ look like the following (with comments removed):
auto requires_(I i) -> decltype(
  concepts::valid_expr(
    concepts::model_of<Convertible, reference_t<I>, common_reference_t<I>>(),
    concepts::model_of<Convertible, value_t<I> &, common_reference_t<I>>(),
    concepts::same_type(indirect_move(i), indirect_move(i, *i))
));

By some additional code navigation I narrowed down a problem to that
DataFrameIterator i; adl_move_detail::indirect_move(i);

wouldn’t compile, again with a “No matching function” error, but that indirect_move(i, *i)did compile. indirect_move(i) however only calls indirect_move(i, *i). However, it takes i as const ref argument, and I thus tried with
const DataFrameIterator i; indirect_move(i, *i);

I now got the informative answer: Indirection requires pointer operand (‘const I’ (aka ‘const RowIterator<self_t>’) invalid), and I could fix the problem to add const to operator*. However, this had taken embarrassingly long (something like two hours) to find a very simple problem.

Duplicate issue #175: "stack trace" like information for concept failure
Created: 2015-06-16T22:05:04Z
Despite the fact range does a great job at constraining algorithm inputs with concepts (Seriously, the interface of the lib is so consistent, I'm sure modeling all the concepts took a lot of work), when something fails I usually find quite hard to know what exact property/rule of the concept was missing on my datatype.
Consider this example: I start writing some kind of range type from scratch, with its own begin() and end() member functions:
struct myrange
{
    using iterator = ...;

    iterator begin() const
    {
        return ...;
    }

    iterator end() const
    {
        return ...;
    }
};
After testing the custom range type via a range for
for(auto e : myrange{...})
    std::cout << e;
everything seems to work as expected, so it's time to apply views on it:
auto view = myrange{...} | ranges::view::transform([](const auto& e){ return ...;});
but Range lib says my range cannot be piped since is not a model of the Iterable concept. To find what the custom range is missing, the only current alternative (since lack of docs, what of course I totally understand and is not the reason for this issue) is to dive into the concept definition, looking  across all the refinements, etc.
After my long voyage across range sources I was able to get the property the iterator was missing and fix my problem.
This and similar experiences while playing with Range give me an idea: What if Concepts carry all the semantic information needed to produce an informative and human readable error message?
That could improve UX a lot. This is the (ideal) error message I would like to see in the example above:
line X: Static assertion failed: The type Rng must be a model of the Iterable concept.
    Iterable: requires "begin(t), end(t)" model "IteratorRange"
        IteratorRange: requires "I" model "Iterator"
                Iterator: refines "WeakIterator", "Semiregular"
                    WeakIterator: refines "WeaklyIncrementable", ...
                        WeaklyIncrementable: refines "Semiregular"
                            SemiRegular : refines ...
                            SemiRegular: requires "&t" of type "T*"
                        WeaklyIncrementable: requires TRUE "std::is_integral<difference_t<I>>{}"
                        WeaklyIncrementable: requires "++i" of type "I&"
                    WeakIterator: requires "*i"
        IteratorRange: requires "S" model "Regular"
            Regular: ...
            ...
        IteratorRange: requires "I, S" model "EqualityComparable"
            EqualityComparable: ...
            ...

As you can see, the error message contains all the semantic and syntactic features required by the main concept, you just have to follow the "stack trace" of concepts. A kind of "compile-time uncaught exception".
The funny part is that most of this information is already encoded in the concepts implementation itself, we just have to provide a way to capture the atomic items (Those enclosed at the requires clauses with "") then concat all the information messages recursively.
This needs to restructure all the concept implementation from its base: The elements of valid_expr() suitable to be SFINAEd  to check for expression correctness (let's call them checkers)  need to have both the expression itself and its stringfied version for the error message. Then valid_expression() needs to concat those strings in addition to be sfinaed. The final error message of a concept is the sum of the string from its requires function and those from the concepts it refines. The whole design looks like a monad holding a tuple (String, SFINAE-able thing) for each check and concept.
I will be glad to spend this summer working on this topic if you find it interesting/useful. This applies to all the libraries relying on C++11 concepts checking out there, like @ldionne's Hana or @pfultz2's Tick.

Closing comment (2015-06-18T17:06:18Z): Closing this as a dupe of #94