Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

result_of for member object with cv-qualified argument

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.

like image 897
Pablo Halpern Avatar asked Aug 28 '16 00:08

Pablo Halpern


2 Answers

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.

like image 106
Pubby Avatar answered Oct 04 '22 17:10

Pubby


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.

like image 24
Oktalist Avatar answered Oct 04 '22 19:10

Oktalist