dramatically improved template error messages (was: C++11 decltype/SFINAE puzzler)

(Andrew, a specific question for you below...) (Moderators: can you see why this msg from Andrew never reached the list (which he cc'ed))? On 8/14/2012 8:39 AM, Andrew Sutton wrote:
GCC-4.7 gives a similarly terse error message. This, I think, is what I've been looking for.
That's neat. Bravo!
I've refined this idea. It now avoids cascading errors by detecting when a sfinae_error is passed as an argument to another function. (For the Haskell-ers out there, that makes it like the Either monad.) You can also get the full backtrace by uncommenting one line of code. IMO, this is now approaching something of real value. My personal experience using this with Proto-11 is that it dramatically improves error messages. I can also easily imagine how this can be used to implement a very nice concept-checking library for C++11 that approximates auto-concepts. Andrew, any thoughts on this? You've done far more work in this space than I have. I'd also like to know *why* this works, and indeed if it's guaranteed to. In particular, is it guaranteed that the virtual what() member of the sfinae_error class is instantiated at the outermost call site and not elsewhere (thereby eliminating the deep template back-trace)? I really just stumbled on this technique by accident, and I don't know how thin the ice is here. Both clang-trunk and gcc-4.7 think it's ok. (I'd also like to apologize to Paul Fultz for spelling his name wrong in an earlier message.) -- Eric Niebler BoostPro Computing http://www.boostpro.com

I'd also like to know *why* this works, and indeed if it's guaranteed to. In particular, is it guaranteed that the virtual what() member of the sfinae_error class is instantiated at the outermost call site and not elsewhere (thereby eliminating the deep template back-trace)? I really just stumbled on this technique by accident, and I don't know how thin the ice is here. Both clang-trunk and gcc-4.7 think it's ok.
I'll wager an explanation... maybe somebody will be able to refine it. Because what() is virtual, it needs to build the vtable for sfinae_error. However, because what() isn't actually used in the program, it is instantiated at the very end of translation. Basically, the point of instantiation is at the top-level, at the end of the translation unit. There's no template instantiation stack to unwind. Comment out the "virtual" keyword in front of what(). The program should actually compile. The try_call doesn't actually force a compilation error, it suppresses it. This only works because sfinae_error happens to be polymorphic. Using unused virtual functions to defer instantiation traps is incredibly clever... If my understanding is correct :)
IMO, this is now approaching something of real value. My personal experience using this with Proto-11 is that it dramatically improves error messages. I can also easily imagine how this can be used to implement a very nice concept-checking library for C++11 that approximates auto-concepts. Andrew, any thoughts on this? You've done far more work in this space than I have.
I want to say yes, but I'm not sure. What's really needed is a combination of this and static_assert. I want compilation to stop *and* I want brief errors. Hmm... Let me get back to you on this.

IMO, this is now approaching something of real value. My personal experience using this with Proto-11 is that it dramatically improves error messages. I can also easily imagine how this can be used to implement a very nice concept-checking library for C++11 that approximates auto-concepts. Andrew, any thoughts on this? You've done far more work in this space than I have.
I want to say yes, but I'm not sure. What's really needed is a combination of this and static_assert. I want compilation to stop *and* I want brief errors. Hmm... Let me get back to you on this.
I tinkered for a little bit, and couldn't get what I wanted. The problem, from the perspective of general concept checking, is that I want compilation to fail when a substitution failure would happen. For example: template <typename I, typename T> I find(I first, I last, const T& value) { static_assert (Input_iterator<I>(), ""); while (first != last && *first != value) ++first; } If a substitution of I triggers the assertion, then (ideally), we would stop instantiating the body and emit an appropriate message. What actually happens is that a diagnostic is reported, then instantiation continues, generating whatever errors I can expect from the expressions in the algorithm (e.g., no operator*). So we get redundancy. If the errors occur in nested instantiations of those expressions, we get template spew. The sfinae_error technique is somewhat different. My characterization is that it is useful for shallow logging of substitution errors, but it won't help here. You could probably force a "logged failure" in place of the static assert, but you'd still get all of the other errors from body of the template. You could make this technique work if you were willing to write all of your algorithms as function objects, and guard all of your calls, but that seems a little intrusive. It's a good trick, but I don't think it scales.

On 8/14/2012 2:19 PM, Andrew Sutton wrote:
IMO, this is now approaching something of real value. My personal experience using this with Proto-11 is that it dramatically improves error messages. I can also easily imagine how this can be used to implement a very nice concept-checking library for C++11 that approximates auto-concepts. Andrew, any thoughts on this? You've done far more work in this space than I have.
I want to say yes, but I'm not sure. What's really needed is a combination of this and static_assert. I want compilation to stop *and* I want brief errors. Hmm... Let me get back to you on this.
I tinkered for a little bit, and couldn't get what I wanted. The problem, from the perspective of general concept checking, is that I want compilation to fail when a substitution failure would happen. For example:
template <typename I, typename T> I find(I first, I last, const T& value) { static_assert (Input_iterator<I>(), ""); while (first != last && *first != value) ++first; }
If a substitution of I triggers the assertion, then (ideally), we would stop instantiating the body and emit an appropriate message. What actually happens is that a diagnostic is reported, then instantiation continues, generating whatever errors I can expect from the expressions in the algorithm (e.g., no operator*). So we get redundancy. If the errors occur in nested instantiations of those expressions, we get template spew.
To avoid redundant errors, you must dispatch to an empty implementation on concept check failure: template <typename I, typename T> I find_impl(std::true_type, I first, I last, const T& value) { while (first != last && *first != value) ++first; } template <typename I, typename T> I find_impl(std::false_type, I first, I last, const T& value) { static_assert (Input_iterator<I>(), ""); } template <typename I, typename T> I find(I first, I last, const T& value) { find_impl(Input_iterator<I>(), first, last, value); }
The sfinae_error technique is somewhat different. My characterization is that it is useful for shallow logging of substitution errors, but it won't help here. You could probably force a "logged failure" in place of the static assert, but you'd still get all of the other errors from body of the template.
Not if you use the trick above.
You could make this technique work if you were willing to write all of your algorithms as function objects,
If you're like me, you write function objects anyway because you prefer first-class functions. :-)
and guard all of your calls, but that seems a little intrusive.
You only have the guard the calls that add constraints. Other calls will simply propagate sfinae_error and don't need to be guarded. (In my example, S2 doesn't need to guard the call to S1, but S1 must guard S0 because it adds an Addable constraint.) Also, you can guard your function object once by defining it with try_call_wrapper, so it doesn't need to be guarded everywhere.
It's a good trick, but I don't think it scales.
Just apply more force. :-) -- Eric Niebler BoostPro Computing http://www.boostpro.com

To avoid redundant errors, you must dispatch to an empty implementation on concept check failure:
template <typename I, typename T> I find_impl(std::true_type, I first, I last, const T& value) { while (first != last && *first != value) ++first; }
template <typename I, typename T> I find_impl(std::false_type, I first, I last, const T& value) { static_assert (Input_iterator<I>(), ""); }
template <typename I, typename T> I find(I first, I last, const T& value) { find_impl(Input_iterator<I>(), first, last, value); }
Not if you use the trick above.
Interesting... That looks like it will work. Do you still need the sfinae_error class to get shallow errors? I'm guessing not in these cases.
You only have the guard the calls that add constraints. Other calls will simply propagate sfinae_error and don't need to be guarded. (In my example, S2 doesn't need to guard the call to S1, but S1 must guard S0 because it adds an Addable constraint.) Also, you can guard your function object once by defining it with try_call_wrapper, so it doesn't need to be guarded everywhere.
Just apply more force. :-)
Indeed :) Side note: sfinae_error is a bad name in a worse way than how "PIN number" is redundant. It's not an error, but it is. substitution_failure or subst_failure might be better choices.

On 8/16/2012 6:44 AM, Andrew Sutton wrote:
To avoid redundant errors, you must dispatch to an empty implementation on concept check failure:
template <typename I, typename T> I find_impl(std::true_type, I first, I last, const T& value) { while (first != last && *first != value) ++first; }
template <typename I, typename T> I find_impl(std::false_type, I first, I last, const T& value) { static_assert (Input_iterator<I>(), ""); }
template <typename I, typename T> I find(I first, I last, const T& value) { find_impl(Input_iterator<I>(), first, last, value); }
Not if you use the trick above.
Interesting... That looks like it will work.
It should. It's an old trick I use in a bunch of places. I even wrote about it here: http://cpp-next.com/archive/2010/09/expressive-c-why-template-errors-suck-an... (See the section "Avoid Follow-on Errors") FWIW, the code I sent above can be improved. The static_assert should be in find(), not find_impl. That will remove one frame of the stack backtrace. And I think the find_impl that takes false_type only needs to be forward-declared. That way you don't need to mock up a return call that will never be used.
Do you still need the sfinae_error class to get shallow errors? I'm guessing not in these cases.
Depends. If you already have an easy way to implement your concept classes so that checking against types that don't model the concept doesn't cause a hard error, then you can forget about the try_call wrapper. Otherwise, it's still useful to you, even though you wouldn't be making use of the short error reporting mechanism.
You only have the guard the calls that add constraints. Other calls will simply propagate sfinae_error and don't need to be guarded. (In my example, S2 doesn't need to guard the call to S1, but S1 must guard S0 because it adds an Addable constraint.) Also, you can guard your function object once by defining it with try_call_wrapper, so it doesn't need to be guarded everywhere.
Just apply more force. :-)
Indeed :)
Side note: sfinae_error is a bad name in a worse way than how "PIN number" is redundant. It's not an error, but it is. substitution_failure or subst_failure might be better choices.
You're right. Thanks. -- Eric Niebler BoostPro Computing http://www.boostpro.com

I tinkered for a little bit, and couldn't get what I wanted. The problem, from the perspective of general concept checking, is that I want compilation to fail when a substitution failure would happen. For example:
template <typename I, typename T> I find(I first, I last, const T& value) { static_assert (Input_iterator<I>(), ""); while (first != last && *first != value) ++first; } This technique comes closer, but you still get errors messages for a missing copy constructor... I think. template <typename I, typename T> I find_impl(I first, I last, const T& value, true_type) { while (first != last && *first != value) ++first;} template <typename I, typename T> I find_impl(I first, I last, const T& value, false_type) { return first;} template <typename I, typename T> I find(I first, I last, const T& value) { static_assert (Input_iterator<I>(), ""); return find_impl(first, last, value, std::integral_constant<bool, Input_iterator<I>()>()); }

On 8/14/2012 1:12 PM, Andrew Sutton wrote:
I'd also like to know *why* this works, and indeed if it's guaranteed to. In particular, is it guaranteed that the virtual what() member of the sfinae_error class is instantiated at the outermost call site and not elsewhere (thereby eliminating the deep template back-trace)? I really just stumbled on this technique by accident, and I don't know how thin the ice is here. Both clang-trunk and gcc-4.7 think it's ok.
I'll wager an explanation... maybe somebody will be able to refine it.
Because what() is virtual, it needs to build the vtable for sfinae_error. However, because what() isn't actually used in the program, it is instantiated at the very end of translation. Basically, the point of instantiation is at the top-level, at the end of the translation unit. There's no template instantiation stack to unwind.
Comment out the "virtual" keyword in front of what(). The program should actually compile. The try_call doesn't actually force a compilation error, it suppresses it. This only works because sfinae_error happens to be polymorphic.
Using unused virtual functions to defer instantiation traps is incredibly clever... If my understanding is correct :)
The technique also works if the what() function is replaced with a non-virtual destructor. So I don't think "virtual" is the magic ingredient here.
IMO, this is now approaching something of real value. My personal experience using this with Proto-11 is that it dramatically improves error messages. I can also easily imagine how this can be used to implement a very nice concept-checking library for C++11 that approximates auto-concepts. Andrew, any thoughts on this? You've done far more work in this space than I have.
I want to say yes, but I'm not sure. What's really needed is a combination of this and static_assert. I want compilation to stop *and* I want brief errors. Hmm... Let me get back to you on this.
Couldn't you avoid generating the error in sfinae_error and instead static_assert that a given function call doesn't have a sfinae_error return type? -- Eric Niebler BoostPro Computing http://www.boostpro.com

Using unused virtual functions to defer instantiation traps is incredibly clever... If my understanding is correct :)
The technique also works if the what() function is replaced with a non-virtual destructor. So I don't think "virtual" is the magic ingredient here.
Shoot. That sounded like such a reasonable explanation, too.
Couldn't you avoid generating the error in sfinae_error and instead static_assert that a given function call doesn't have a sfinae_error return type?
You could (that's basically how Origin's concept checking stuff works), but you'd still have the problem that every expression in the constrained template will still be instantiated and type-checked so you'll get errors.
participants (3)
-
Ahmed Charles
-
Andrew Sutton
-
Eric Niebler