[result_of] Usage guidelines

Hi, I wonder what is the correct way of declaring polymorphic function object result types and how to use result_of template with regard to argument types. If a function object receives arguments by reference, should its member result<> template be specialized for reference types as well? Also, should result_of<> for this function be instantiated with reference argument types? struct my_foo { template< typename > struct result; // Is this correct? template< typename ArgT > struct result< my_foo(ArgT) > { typedef ArgT type; }; // Or is this correct? template< typename ArgT > struct result< my_foo(ArgT const&) > { typedef ArgT type; }; template< typename ArgT > ArgT operator() (ArgT const& arg) const { return arg; } }; I realize that I can provide both result<> specializations and it will work either way. But when there are more than one argument the specializations begin to pile quickly. Consider also rvalue references and const and non-const qualified my_foo. Another question I have is how to use result_of<> when my_foo is external (i.e. provided by a third party library)? // Which one is the right way? typedef result_of< my_foo(int) >::type result_type1; typedef result_of< my_foo(int const&) >::type result_type2; I cannot know what result<> specializations are provided by my_foo, so probably there isn't an ultimate answer. But perhaps there are some guidelines or best practices? I've looked at result_of docs but there are no recommendations on this.

[The following expresses the guidelines I've been using, which may or may not be the official Boost.ResultOf guidelines. But they make the most sense to me.] On Mon, Sep 3, 2012 at 12:08 AM, Andrey Semashev <andrey.semashev@gmail.com>wrote:
Hi,
I wonder what is the correct way of declaring polymorphic function object result types and how to use result_of template with regard to argument types. If a function object receives arguments by reference, should its member result<> template be specialized for reference types as well?
Not necessarily. I believe it is sufficient to sprinkle some remove_reference's and remove_const's around if the cv and reference qualifiers of the arguments don't matter. Also, should result_of<> for this function be instantiated
with reference argument types?
Depends if the arguments of the actual call expression you're interested in are lvalues or rvalues. struct my_foo
{ template< typename > struct result;
// Is this correct? template< typename ArgT > struct result< my_foo(ArgT) > { typedef ArgT type; };
No.
// Or is this correct? template< typename ArgT > struct result< my_foo(ArgT const&) > { typedef ArgT type; };
Not quite (based on your overload of operator() below). Use the former signature, with a "typedef typename remove_cv< typename remove_reference< ArgT >::type >::type". The latter composition of metafunctions occurs so frequently that I just use a remove_qualifiers that is equivalent to remove_cv< remove_reference<T>::type >::type.
template< typename ArgT > ArgT operator() (ArgT const& arg) const { return arg; } };
I realize that I can provide both result<> specializations and it will work either way. But when there are more than one argument the specializations begin to pile quickly. Consider also rvalue references and const and non-const qualified my_foo.
You typically only have to provide as many specializations of result<> (directly and indirectly) as you have overloads of operator(), and quite often fewer. A little metaprogramming regarding cv and reference qualifiers might be necessary, though :) Another question I have is how to use result_of<> when my_foo is
external (i.e. provided by a third party library)?
// Which one is the right way? typedef result_of< my_foo(int) >::type result_type1; typedef result_of< my_foo(int const&) >::type result_type2;
Again, depends on if you call it like int x = 0; my_foo(x); // result_of< my_foo( int & ) >::type or int const x = 0; my_foo(x); // result_of< my_foo( int const & ) >::type or my_foo(0); // result_of< my_foo( int ) >::type
I cannot know what result<> specializations are provided by my_foo, so probably there isn't an ultimate answer.
True; you hope result<> specializations are done carefully and thoughtfully :/
But perhaps there are some guidelines or best practices? I've looked at result_of docs but there are no recommendations on this.
Someone may chime in and refute my position on this :) - Jeff

On Monday 03 September 2012 13:53:18 Jeffrey Lee Hellrung, Jr. wrote:
On Mon, Sep 3, 2012 at 12:08 AM, Andrey Semashev
<andrey.semashev@gmail.com>wrote:
Hi,
I wonder what is the correct way of declaring polymorphic function object result types and how to use result_of template with regard to argument types. If a function object receives arguments by reference, should its member result<> template be specialized for reference types as well?
Not necessarily. I believe it is sufficient to sprinkle some remove_reference's and remove_const's around if the cv and reference qualifiers of the arguments don't matter.
[snip]
// Or is this correct? template< typename ArgT > struct result< my_foo(ArgT const&) > {
typedef ArgT type;
};
Not quite (based on your overload of operator() below). Use the former signature, with a "typedef typename remove_cv< typename remove_reference< ArgT >::type >::type". The latter composition of metafunctions occurs so frequently that I just use a remove_qualifiers that is equivalent to remove_cv< remove_reference<T>::type >::type.
Yes, this seem to simplify things a lot, thanks for the hint. It gives the correct result type for most cases except when argument type is a non-const lvalue reference. In this case incorrect result_of instantiations will also work, which may not be expected. I suppose, in such cases additional specializations are still needed: struct my_foo { template< typename > struct result; template< typename ArgT > struct result< my_foo(ArgT const&) >; template< typename ArgT > struct result< my_foo(ArgT&) > { typedef ArgT type; }; template< typename ArgT > ArgT operator() (ArgT& arg) const { return arg; } };
Another question I have is how to use result_of<> when my_foo is
external (i.e. provided by a third party library)?
// Which one is the right way? typedef result_of< my_foo(int) >::type result_type1; typedef result_of< my_foo(int const&) >::type result_type2;
Again, depends on if you call it like
int x = 0; my_foo(x); // result_of< my_foo( int & ) >::type
or
int const x = 0; my_foo(x); // result_of< my_foo( int const & ) >::type
or
my_foo(0); // result_of< my_foo( int ) >::type
Ok, so the rule of thumb is to always instantiate result_of on the exact (reference) types of arguments you have and let the result<> specializations deal with references. Makes sense, thank you.

On 04/09/2012 08:59, Andrey Semashev wrote:
I suppose, in such cases additional specializations are still needed:
struct my_foo { template< typename > struct result;
template< typename ArgT > struct result< my_foo(ArgT const&) >;
I assume you meant template< typename ArgT > struct result< my_foo(ArgT const&) > { typedef ArgT type; }; there?
template< typename ArgT > struct result< my_foo(ArgT&) > { typedef ArgT type; };
template< typename ArgT > ArgT operator() (ArgT& arg) const { return arg; } };
1) doesn't support result_of<my_foo(int)> 2) not always the same type as what operator() returns (consider a const lvalue passed to operator()) 3) needless repetition You probably wanted to write struct my_foo { template<class Sig> struct result; template<class This, class A0> struct result<This(A0)> : strip<A0> {}; template<class A0> typename remove_const<A0>::type operator()(A0& a0) const { return a0; } };

On Tuesday 04 September 2012 10:06:32 Mathias Gaunard wrote:
On 04/09/2012 08:59, Andrey Semashev wrote:
I suppose, in such cases additional
specializations are still needed: struct my_foo {
template< typename > struct result;
template< typename ArgT > struct result< my_foo(ArgT const&) >;
I assume you meant
template< typename ArgT > struct result< my_foo(ArgT const&) > { typedef ArgT type; };
there?
No, my intent was to disable the const& version, because the operator() doesn't support it. However, now that you mentioned it, what I really wanted to express is that my operator() requires a non-const lvalue argument and the way operator() is written it doesn't communicate that clearly (because ArgT can be const).
template< typename ArgT > struct result< my_foo(ArgT&) > {
typedef ArgT type;
};
template< typename ArgT > ArgT operator() (ArgT& arg) const {
return arg;
}
};
1) doesn't support result_of<my_foo(int)> 2) not always the same type as what operator() returns (consider a const lvalue passed to operator()) 3) needless repetition
You probably wanted to write
struct my_foo { template<class Sig> struct result;
template<class This, class A0> struct result<This(A0)> : strip<A0> {};
template<class A0> typename remove_const<A0>::type operator()(A0& a0) const { return a0; } };
No, see my note above. For sake of the example, let's assume my_foo does a post-increment on its argument. template<class A0> typename disable_if< is_const<A0>, A0 >::type operator()(A0& a0) const { return a0++; } Also, what is strip in your example? I didn't find it in type traits.

On 04/09/2012 10:43, Andrey Semashev wrote:
No, my intent was to disable the const& version, because the operator() doesn't support it.
I see. You could have used SFINAE to disable the result too, I guess.
Also, what is strip in your example? I didn't find it in type traits.
It's not from type traits, it's a meta-function that removes cv and reference qualifiers.

On 03/09/2012 09:08, Andrey Semashev wrote:
I realize that I can provide both result<> specializations and it will work either way. But when there are more than one argument the specializations begin to pile quickly. Consider also rvalue references and const and non-const qualified my_foo.
I have yet to find a good way to do this myself. It seems it is an issue that really needs some written best practices though, since a lot of libraries that define result appear to not to it vert well (sadly, even Phoenix is among them; it uses more specializations than necessary and simple things like result_of<F(int, int, int&)> are ill-formed).

On Tuesday 04 September 2012 06:00:39 Mathias Gaunard wrote:
On 03/09/2012 09:08, Andrey Semashev wrote:
I realize that I can provide both result<> specializations and it will work either way. But when there are more than one argument the specializations begin to pile quickly. Consider also rvalue references and const and non-const qualified my_foo.
I have yet to find a good way to do this myself. It seems it is an issue that really needs some written best practices though, since a lot of libraries that define result appear to not to it vert well (sadly, even Phoenix is among them; it uses more specializations than necessary and simple things like result_of<F(int, int, int&)> are ill-formed).
Yes, actually, I started asking these questions while I was working with Phoenix. :) Jeffrey have suggested a good solution in his reply. I think it can be described in the result_of docs to resolve the confusion in the future. I can update the docs if noone objects.

On Tuesday 04 September 2012 11:07:39 you wrote:
Jeffrey have suggested a good solution in his reply. I think it can be described in the result_of docs to resolve the confusion in the future. I can update the docs if noone objects.
I've added a clarification note to the result_of docs in trunk. Here is the added section: <quote> The result template must be specialized for every valid calling signature of the function object. If the operator() accepts arguments by (possibly const) reference and/or is const qualified, the result specialization must take this into account. Type traits and more generic specializations may help to reduce the number of result specializations. This way result_of users will be able to specify argument types exactly according to the function object call expression. For example: struct functor { template<class> struct result; // Use template parameter F to match the function object. This will allow result deduction for both const and non-const functor. template<class F, class T> struct result<F(T)> { // When argument type is matched like above, remember that the type may be a (const-qualified) reference. // Use type traits to transform the argument type. typedef typename remove_cv<typename remove_reference<T>::type>::type argument_type; typedef argument_type type; }; // The operator can be called on both const and non-const functor. The argument can be lvalue or rvalue. template<class T> T operator()(T const& x) const { return x; } }; // All following result_of uses are valid and result in int typedef boost::result_of< functor(int) >::type type1; // the argument is rvalue functor f; type1 r1 = f(10); typedef boost::result_of< const functor(int) >::type type2; // the function object is const const functor cf; type2 r2 = cf(10); typedef boost::result_of< functor(int&) >::type type3; // the argument is lvalue int a = 10; type3 r3 = f(a); typedef boost::result_of< functor(int const&) >::type type4; // the argument is const lvalue const int ca = 10; type4 r4 = f(ca); </quote> You can find the complete page tere: http://svn.boost.org/svn/boost/trunk/libs/utility/utility.htm If noone objects I will merge it to release branch in a few days.

On Sat, Sep 8, 2012 at 6:59 AM, Andrey Semashev <andrey.semashev@gmail.com>wrote:
On Tuesday 04 September 2012 11:07:39 you wrote:
Jeffrey have suggested a good solution in his reply. I think it can be described in the result_of docs to resolve the confusion in the future. I can update the docs if noone objects.
I've added a clarification note to the result_of docs in trunk. Here is the added section:
<quote> The result template must be specialized for every valid calling signature of the function object. If the operator() accepts arguments by (possibly const) reference and/or is const qualified,
My first pass over this read the "const qualified" as applying to the arguments to operator(), but it actually applied to operator() itself. That indicates that it may be good to reword to clarify.
the result specialization must take this into account. Type traits and more generic specializations may help to reduce the number of result specializations.
On a second reading through, I think something like the following may be a bit clearer: -------- The result template should be specialized to account for all possible valid call signatures of the function object, including any const-qualification of the function object itself as well as reference- or reference-to-const-qualifications of the argument types. One may infer the set of valid call signatures based on what combinations of (possibly reference- or reference-to-const-qualified) arguments are accepted by the set of operator() overloads. Type trait metafunctions (such as Boost.TypeTraits' remove_const [linked(?)] and remove_reference [linked(?)]) and appropriate specializations can often reduce the number of result template specializations to a minimum while ensuring that all valid call signatures are covered. -------- Hmmm...actually, the more I'm reading your original paragraph, the more it's really saying the same thing as above, just with more words, so I don't know what was unclear to me before. Take from what I wrote above what you think might be appropriate (if anything) without complicating things too much. This way result_of users will be able to
specify argument types exactly according to the function object call expression. For example:
struct functor { template<class> struct result;
// Use template parameter F to match the function object. This will allow result deduction for both const and non-const functor. template<class F, class T> struct result<F(T)> { // When argument type is matched like above, remember that the type may be a (const-qualified) reference. // Use type traits to transform the argument type. typedef typename remove_cv<typename remove_reference<T>::type>::type argument_type; typedef argument_type type; };
// The operator can be called on both const and non-const functor. The argument can be lvalue or rvalue. template<class T> T operator()(T const& x) const { return x; } };
// All following result_of uses are valid and result in int typedef boost::result_of< functor(int) >::type type1; // the argument is rvalue functor f; type1 r1 = f(10);
typedef boost::result_of< const functor(int) >::type type2; // the function object is const const functor cf; type2 r2 = cf(10);
typedef boost::result_of< functor(int&) >::type type3; // the argument is lvalue int a = 10; type3 r3 = f(a);
typedef boost::result_of< functor(int const&) >::type type4; // the argument is const lvalue const int ca = 10; type4 r4 = f(ca); </quote>
You can find the complete page tere:
http://svn.boost.org/svn/boost/trunk/libs/utility/utility.htm
If noone objects I will merge it to release branch in a few days
- Jeff

On Sep 8, 2012, at 9:59 AM, Andrey Semashev wrote:
On Tuesday 04 September 2012 11:07:39 you wrote:
Jeffrey have suggested a good solution in his reply. I think it can be described in the result_of docs to resolve the confusion in the future. I can update the docs if noone objects.
I've added a clarification note to the result_of docs in trunk. Here is the added section:
Thanks for the suggestion Andrey. Using remove_cv is a good tip, and I think it would be useful to have a tips and/or cookbook section. But a change like this should really be discussed on the list before committing to trunk. I've reverted your change, since discussion is still underway. I just wouldn't want something like this to accidentally slip into a release without being reviewed. My specific concern is that your tip is more appropriate for an advanced user than a complete novice, and I wouldn't want to confuse or misled the novice. This thread has been interesting and I've enjoyed following it. There have been several good comments that I think can be compiled in a convenient way for users and placed in the docs.
<quote> The result template must be specialized for every valid calling signature of the function object.
The rest of your example seems reasonable, but this sentence is not entirely true. The result template is not required at all in C++11; it is only needed for C++03 and for portability to compilers lacking sufficient decltype support. Even on C++03 the result template is only needed when the return type varies between overloads. If the return type is constant, you can skip the whole hassle and just define result_type. I wouldn't want a novice to read that sentence and then start spending time specializing the result template when he or she never actually need it. Here's a rough outline of what I think is important for the guidelines to convey. 1) If you are targeting C++11 and are not concerned about portability to non-compliant compilers or previous versions of the standard, then use std::result_of. 2) If you are targeting C++11 but may port your code to legacy compilers sometime in the future, use boost::result_of with decltype. 3) If compiler portability is required, use boost::result_of with the TR1 protocol. The point I'm trying to emphasis is that users only need to incur the expense of the TR1 protocol when they want the benefit of portability.
If the operator() accepts arguments by (possibly const) reference and/or is const qualified, the result specialization must take this into account. Type traits and more generic specializations may help to reduce the number of result specializations. This way result_of users will be able to specify argument types exactly according to the function object call expression. For example:
struct functor { template<class> struct result;
// Use template parameter F to match the function object. This will allow result deduction for both const and non-const functor. template<class F, class T> struct result<F(T)> { // When argument type is matched like above, remember that the type may be a (const-qualified) reference. // Use type traits to transform the argument type. typedef typename remove_cv<typename remove_reference<T>::type>::type argument_type; typedef argument_type type; };
// The operator can be called on both const and non-const functor. The argument can be lvalue or rvalue. template<class T> T operator()(T const& x) const { return x; } };
This is a good example.
// All following result_of uses are valid and result in int typedef boost::result_of< functor(int) >::type type1; // the argument is rvalue functor f; type1 r1 = f(10);
typedef boost::result_of< const functor(int) >::type type2; // the function object is const const functor cf; type2 r2 = cf(10);
typedef boost::result_of< functor(int&) >::type type3; // the argument is lvalue int a = 10; type3 r3 = f(a);
typedef boost::result_of< functor(int const&) >::type type4; // the argument is const lvalue const int ca = 10; type4 r4 = f(ca); </quote>
I'm not sure that including verbose, repetitive client code clarifies anything. It might be better to just demonstrate the result template usage. - Daniel

On 9/4/2012 12:00 PM, Mathias Gaunard wrote:
On 03/09/2012 09:08, Andrey Semashev wrote:
I realize that I can provide both result<> specializations and it will work either way. But when there are more than one argument the specializations begin to pile quickly. Consider also rvalue references and const and non-const qualified my_foo.
I have yet to find a good way to do this myself. It seems it is an issue that really needs some written best practices though, since a lot of libraries that define result appear to not to it vert well (sadly, even Phoenix is among them; it uses more specializations than necessary and simple things like result_of<F(int, int, int&)> are ill-formed).
I haven't looked at Phoenix yet (will do). However, my recent investigations with fusion::invoke reveal that the problems we see are not necessarily from misuse of result_of (bugs from flouting the result_of rules) as people summarily think. See my other post. Regards, -- Joel de Guzman http://www.boostpro.com http://boost-spirit.com
participants (5)
-
Andrey Semashev
-
Daniel Walker
-
Jeffrey Lee Hellrung, Jr.
-
Joel de Guzman
-
Mathias Gaunard