[Proto][Phoenix] Use default transform with SFINAE for expressions?

I would quite like it if polymorphic function objects created by Phoenix could restrict overload resolution to types for which their function body is valid, using SFINAE for expressions. Basically, this would allow something like this: struct foo {}; void dummy_function(...) { std::cout << "[not streamable]"; } make_overload( std::cout << arg1, dummy_function ) (foo()); (with make_overload what you'd expect) The dummy function would be called if the first one doesn't match, instead of resulting in a hard error. (surely it would also be nice to add a module to create "..." functions in Phoenix, but that should be easy) Now I looked a bit at how Phoenix v3 and Proto work, and it seems the main problem to do that is in the default transform. Albeit it uses decltype for its return type, this is not done in a template deduction context, which prevents it from doing its SFINAE magic. I tried to do that, but then a transform is assumed everywhere to be monomorphic, so SFINAE doesn't "propagate" out of default_TAG. Before I go ahead and try to convert all result_type to result with templates, which could affect compile-time, I'd like to have some feedback on the whole idea, and whether this is the right way to do it.

Mathias Gaunard wrote:
I would quite like it if polymorphic function objects created by Phoenix could restrict overload resolution to types for which their function body is valid, using SFINAE for expressions.
Basically, this would allow something like this:
struct foo {};
void dummy_function(...) { std::cout << "[not streamable]"; }
make_overload( std::cout << arg1, dummy_function ) (foo());
(with make_overload what you'd expect)
The dummy function would be called if the first one doesn't match, instead of resulting in a hard error. (surely it would also be nice to add a module to create "..." functions in Phoenix, but that should be easy)
Now I looked a bit at how Phoenix v3 and Proto work, and it seems the main problem to do that is in the default transform.
Albeit it uses decltype for its return type, this is not done in a template deduction context, which prevents it from doing its SFINAE magic. I tried to do that, but then a transform is assumed everywhere to be monomorphic, so SFINAE doesn't "propagate" out of default_TAG.
Before I go ahead and try to convert all result_type to result with templates, which could affect compile-time, I'd like to have some feedback on the whole idea, and whether this is the right way to do it.
Phoenix3 uses result_of protocol, and result_type wherever the return type is monomorphic. My take would be to use boost::result_of<Expr(T1, T2, ...)>::type before calling the phoenix expression and use SFINAE here.

On 12/09/2010 09:18, Thomas Heller wrote:
Phoenix3 uses result_of protocol, and result_type wherever the return type is monomorphic.
It uses result_of on the grammar, which in turns calls apply_transform on the default transform, the latter not happening in a template deduction context and thus preventing SFINAE to happen.
My take would be to use boost::result_of<Expr(T1, T2, ...)>::type before calling the phoenix expression and use SFINAE here.
I don't understand what you mean, and how that would help at all. What needs to be in an SFINAE context, here, is something of the kind decltype(make_mutable<T0>() << make_mutable<T1>()), with T0 and T1 template parameters. If you try the above code, you'll get an error in the default_shift_left transform, which already uses decltype to deduce the return type, but not in a template context. If you change default_shift_left's operator() to be a template and change its result type deduction to be in a template context as well, then you get an error that says no overload matches within default_shift_left. That's because the SFINAE doesn't propagate up to the grammar through apply_transform etc., and making that work involves doing the changes that I said. It's really a limitation in Proto at this point, not in Phoenix.

On Sun, Sep 12, 2010 at 8:11 AM, Mathias Gaunard <mathias.gaunard@ens-lyon.org> wrote:
On 12/09/2010 09:18, Thomas Heller wrote:
My take would be to use boost::result_of<Expr(T1, T2, ...)>::type before calling the phoenix expression and use SFINAE here.
I don't understand what you mean, and how that would help at all.
What needs to be in an SFINAE context, here, is something of the kind decltype(make_mutable<T0>() << make_mutable<T1>()), with T0 and T1 template parameters.
I'm not sure if this could work. The C++0x draft is pretty specific about the context in which substitution failure is allowed, namely, it's only allowed in the immediate context of the function and not as a side effect of instantiating a template (such as a Proto class). Here's the relevant language from 14.8.2. "Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure. [ Note: The evaluation of the substituted types and expressions can result in side effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-defined functions, etc. Such side effects are not in the “immediate context” and can result in the program being ill-formed. — end note ]" However, I just checked, and I was pleased to find that gcc 4.5 is now accepting some SFINAE for expressions. typedef char (&pass)[1]; typedef char (&fail)[2]; template<class T> fail has_addition(...); template<class T> pass has_addition(decltype(T() + T())*); struct S { }; int main() { static_assert( sizeof(has_addition<int>(0)) == sizeof(pass) , "unexpected substitution failure" ); static_assert( sizeof(has_addition<S>(0)) == sizeof(fail) , "unexpected substitution success" ); } I think this method could be developed, eventually, to enable rich, expression-based introspection of types. Daniel Walker

On 12/09/2010 21:04, Daniel Walker wrote:
I'm not sure if this could work. The C++0x draft is pretty specific about the context in which substitution failure is allowed, namely, it's only allowed in the immediate context of the function and not as a side effect of instantiating a template (such as a Proto class). Here's the relevant language from 14.8.2.
SFINAE does apply in that kind of context: template<typename T, typename U> decltype(make<T>() + make<U>()) add(T t, U u) { return t + u; } The overload is not considered if the expression isn't valid. Of course this works with sizeof too, and in C++03 as well (if the compilers supports it). This is the kind of thing I want Proto's default transform to do.
However, I just checked, and I was pleased to find that gcc 4.5 is now accepting some SFINAE for expressions.
It's been there since 4.4 I think, but has only been working well since 4.5. I've put a macro for it in Boost.Config a while ago.

On Sun, Sep 12, 2010 at 10:38 PM, Mathias Gaunard <mathias.gaunard@ens-lyon.org> wrote:
On 12/09/2010 21:04, Daniel Walker wrote:
I'm not sure if this could work. The C++0x draft is pretty specific about the context in which substitution failure is allowed, namely, it's only allowed in the immediate context of the function and not as a side effect of instantiating a template (such as a Proto class). Here's the relevant language from 14.8.2.
SFINAE does apply in that kind of context:
template<typename T, typename U> decltype(make<T>() + make<U>()) add(T t, U u) { return t + u; }
The overload is not considered if the expression isn't valid. Of course this works with sizeof too, and in C++03 as well (if the compilers supports it).
This is the kind of thing I want Proto's default transform to do.
Actually, I think expression SFINAE may have been a non-standard, language extension prior to C++0x, or at least, the new standard makes it explicit. And sorry, I didn't read your previous example closely enough; surely the error is not a side effect of instantiating make_mutable, so yes, that kind of context should allow SFINAE. For what it's worth, your suggestion to convert the return types to be in template deduction context seems reasonable to me, but I'm afraid I'm not familiar enough with Proto to give a well informed opinion as to the trade-offs.
However, I just checked, and I was pleased to find that gcc 4.5 is now accepting some SFINAE for expressions.
It's been there since 4.4 I think, but has only been working well since 4.5. I've put a macro for it in Boost.Config a while ago.
You're right, I checked and gcc 4.4 accepted my example. Thanks! What I had in mind for the example is to illustrate the feasibility of a concept checking library that issues substitution failures rather than compilation errors when a type doesn't model a required concept. This would provide an API to restrict the compiler such that it considers only overloads for which the argument types meet the requirements of the function body. It might be possible to approximate the functionality of the "requires" keyword from ConceptC++ without requiring language extensions to C++0x. But this would be a long-term, general solution. In the mean time, I'm not sure what to do about your immediate problem. Daniel Walker

On 13/09/2010 18:03, Daniel Walker wrote:
What I had in mind for the example is to illustrate the feasibility of a concept checking library that issues substitution failures rather than compilation errors when a type doesn't model a required concept. This would provide an API to restrict the compiler such that it considers only overloads for which the argument types meet the requirements of the function body. It might be possible to approximate the functionality of the "requires" keyword from ConceptC++ without requiring language extensions to C++0x. But this would be a long-term, general solution. In the mean time, I'm not sure what to do about your immediate problem.
Right, I've been saying that for a while. Concepts being dropped from C++0x is not such a big deal since SFINAE can do the job anyway. I've had plans for a while to rewrite Boost.ConceptCheck to do that, but unfortunately the interface would need to change, because Boost.ConceptCheck allows arbitrary statements instead of just expressions. However, SFINAE has a few caveats compared to concepts: - public/private/protected access control is done after template substitution, so you can get a hard error because of these - it doesn't provide structural subtyping, i.e., it doesn't order the overloads by quality of match. But in practice this isn't much of a problem because you want to define concepts nominally anyway, and when you do that you can specify what you refine. - it doesn't support retroactive modelling, but that kind of thing can be done with traits.

On Sat, Sep 11, 2010 at 8:40 PM, Mathias Gaunard <mathias.gaunard@ens-lyon.org> wrote:
I would quite like it if polymorphic function objects created by Phoenix could restrict overload resolution to types for which their function body is valid, using SFINAE for expressions.
This is the holy grail of C++ generic programming. :-) It would allow you to overload on concepts. Daniel Walker
participants (3)
-
Daniel Walker
-
Mathias Gaunard
-
Thomas Heller