20

Given the following declarations:

struct MyClass { };
typedef int MyClass::*Mp;

On both gcc 6.2 and Clang compiler I have tried, result_of<Mp(const MyClass)>::type yields int&&.

Summary of my question: Why int&& and not either const int&& or simply int?

More Background: The standard says that result_of is defined this way:

the member typedef type shall name the type decltype(INVOKE(declval<Fn>(), declval<ArgTypes>()...));

The standard also defines INVOKE for pointer-to-member-objects this way:

— t1.*f when N == 1 and f is a pointer to data member of a class T and is_base_of_v<T, decay_t<decltype(t1)>> is true;

Note that the decay_t is only for testing whether this bullet applies. As far as I can tell, applying the two points above should yield:

decltype(declval<const MyClass>().*declval<Mp>())

Which yields const int&&. So, am I missing something, or are the compiler libraries wrong?

Edit, 30 Aug 2016:

Thanks for the responses. Several people have suggested alternative ways of getting the correct result without using result_of. I should clarify that the reason I am hung up on the correct definition of result_of is that I'm actually implementing the closest reasonable implementation of result_of that works with a pre-C++11 compiler. So, while I agree that I can use decltype or result_of<Mp(const MyClass&&)>::type in C++11, they do not do what I need for C++03. Several people have given the correct answer, which is that const rvalue arguments to functions are not part of the function type. This clarifies things for me and I will implement my pre-C++11 result_of such that it also discards those qualifiers.

Pablo Halpern
  • 941
  • 7
  • 12
  • Um, I think you have a typo in the member function declaration? Or am I missing something? – Yakk - Adam Nevraumont Aug 28 '16 at 00:44
  • I _think_ I read in _Stroustrup_'s book that `const int&&` makes no sense (as the whole idea of an r-value is that it _can_ be moved, and `const` blocks that), so the `const` gets automatically removed. – Aganju Aug 28 '16 at 01:46
  • @Yakk, there is no member function declaration. It is a pointer-to-member-object (data) declaration that is being used. @Aganju, Stroustrup is right that `const int&&` has limited usefulness BUT it does *make sense*, is defined in the language, and is supported by compilers. In fact, it's appearance is an artifact of constructs like this one, and I don't see anything in the specification that would prevent it from occurring here. (I am a member of the C++ standards committee, so I might ask this same question in the standards email list.) – Pablo Halpern Aug 28 '16 at 02:11

2 Answers2

8

const is stripped from function parameters. You can verify this using is_same.

void(int) == void(const int)
Mp(MyClass) == Mp(const MyClass)
result_of<Mp(MyClass)> == result_of<Mp(const MyClass)>

I think this is explained by [8.3.5.5]:

After producing the list of parameter types, any top-level cv-qualifiers modifying a parameter type are deleted when forming the function type. The resulting list of transformed parameter types and the presence or absence of the ellipsis or a function parameter pack is the function’s parameter-type-list. [ Note: This transformation does not affect the types of the parameters. For example, int(*)(const int p, decltype(p)*) and int(*)(int, const int*) are identical types. — end note ]

You can work around it by defining your own result_of that does not (mis)use function types:

template <typename F, typename... ArgTypes>
struct my_result_of
{
    using type = decltype(std::invoke(std::declval<F>(), std::declval<ArgTypes>()...));
};

This definition is really what the standard should have used.

Pubby
  • 51,882
  • 13
  • 139
  • 180
  • Thanks for citing the section of the standard. I thought this might be the case, but some experiments made me doubt myself. I'm wondering if the compilers are actually retaining the CV qualification on function parameters sometimes, and discarding them other times. More experimentation needed. – Pablo Halpern Aug 28 '16 at 04:44
  • @PabloHalpern Given `void f(const int x) {}` the type of `f` is `void(int)` but `x` is a `const` variable _inside the body_ of `f`. – Oktalist Aug 28 '16 at 12:33
  • @PabloHalpern maybe you are thinking of http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1322 and its relatives – Johannes Schaub - litb Aug 28 '16 at 19:05
  • For future readers: `my_result_of` has been included in C++17 as `invoke_result` – L. F. May 03 '20 at 06:48
5

In result_of_t<Mp(const MyClass)> you appear to be trying to ask what is the type of the result of invoking Mp with a const rvalue of type MyClass. A better way to ask that with result_of would be result_of_t<Mp(const MyClass&&)> but it's usually easier to just use decltype and forget that result_of ever existed. If you actually intended to ask the result with a const lvalue then that would be result_of_t<Mp(const MyClass&)>.

It is true that top-level const on function parameters has no meaning in a function declaration. When using result_of, therefore, it makes more sense to supply argument types as references to possibly-const types. This also makes the value category explicit, with no loss of expressivity. We can use the print_type trick to see what happens when we do this:

template <typename...> struct print_type; // forward declaration

print_type<std::result_of_t<Mp(const MyClass)>,
           std::result_of_t<Mp(const MyClass&)>,
           std::result_of_t<Mp(const MyClass&&)>,
           std::result_of_t<Mp(MyClass)>,
           std::result_of_t<Mp(MyClass&)>,
           std::result_of_t<Mp(MyClass&&)>>{};

This prints:

error: invalid use of incomplete type 'struct print_type<int&&, const int&, const int&&, int&&, int&, int&&>'

So we can deduce:

std::result_of_t<Mp(const MyClass)>   == int&&
std::result_of_t<Mp(const MyClass&)>  == const int&
std::result_of_t<Mp(const MyClass&&)> == const int&&
std::result_of_t<Mp(MyClass)>         == int&&
std::result_of_t<Mp(MyClass&)>        == int&
std::result_of_t<Mp(MyClass&&)>       == int&&

We can see that result_of_t<Mp(const MyClass)>, result_of_t<Mp(MyClass)>, and result_of_t<Mp(MyClass&&)> all mean the same thing. I would find it surprising if they didn't.

Note that when you use declval you are also providing argument types as references, as declval is declared to return a reference. Furthermore, all parameters to std::invoke are references.

Oktalist
  • 14,336
  • 3
  • 43
  • 63