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.
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.
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.
Related
If I define a function which accepts an rvalue reference parameter:
template <typename T>
void fooT(T &&x) {}
I can call it, using GCC 4.5, with either a, ar, or arr:
int a, &ar = a, &&arr = 7;
fooT(a); fooT(ar); fooT(arr);
However, calling a similar, non-template function,
void fooInt(int &&x) {}
with any of those three arguments will fail. I was preparing to strengthen my knowledge of forward, but this has knocked me off course. Perhaps it's GCC 4.5; I was surprised to find that the first example from A Brief Introduction to Rvalue References also gives a compile error:
A a;
A&& a_ref2 = a; // an rvalue reference
The behavior of deduction in template parameters is unique, and is the reason your template version works. I've explained exactly how this deduction works here, in the context of another question.
Summarized: when the argument is an lvalue, T is deduced to T&, and T& && collapses to T&. And with the parameter at T&, it is perfectly valid to supply an lvalue T to it. Otherwise, T remains T, and the parameter is T&&, which accepts rvalues arguments.
Contrarily, int&& is always int&& (no template deduction rules to coerce it to something else), and can only bind to rvalues.
In addition to GMan's correct answer A Brief Introduction to Rvalue References has an incorrect example because it was written prior to a language change which outlawed:
A a;
A&& a_ref2 = a; // an rvalue reference (DISALLOWED in C++11)
Despite this change in the language, the main uses cases described in the article (move and forward) are still explained correctly in the article.
Update: Oh, and the same article was originally published here with (imho) slightly better formatting.
#include <iostream>
template <typename T>
class test
{
public:
test(T&& t)
{
}
};
template <typename T>
void succeed(T&& t)
{
}
int main()
{
int i = 1;
test<int> t(i); // failed to compile
succeed(i); // OK
return 0;
}
Error from GCC 5.2:
main.cpp: In function 'int main()':
main.cpp:20:18: error: cannot bind 'int' lvalue to 'int&&'
test t(i);
^
main.cpp:7:5: note: initializing argument 1 of 'test::test(T&&) [with T = int]'
test(T&& t)
^~~~
Could someone explain why the class template cannot compile but function template is OK?
Thanks.
In succeed, T&& t is a forwarding reference, not an rvalue reference. But in test, it is an rvalue reference.
A forwarding reference happens only when the parameter is T&&, and T is a template parameter of that function. In your code T is a template parameter of the enclosing class, so it doesn't count as a forwarding reference.
A forwarding reference may bind to both lvalues and rvalues.
During the drafting of C++11 it was suggested to use different syntax for forwarding references than rvalue references (instead of using T&& t for both); however the committee eventually settled on the current behaviour.
For a more detailed description of template parameter deduction, including a more precise specification of when T&& becomes a forwarding reference, see here -- search for the term "forwarding reference" to find the special rules for forwarding references.
Your confusion is probably rooted in your assumption that in both cases T is int. This is why you presume that these two cases as similar. In reality they are not.
In the class version you are manually specifying what T is. You explicitly tell the compiler that T is int. Constructor parameter type T && in this case becomes int &&, which cannot bind to a regular lvalue. Hence the error.
In the function version you don't tell the compiler what T is, but instead you expect the compiler to deduce it. In situations like yours the language is deliberately designed to deduce T as int & (note: not as int, but rather as int &). Once T is deduced as int &, the so called "reference collapsing" rules lead to function parameter type T && becoming int & - an ordinary lvalue reference. This parameter can successfully bind to lvalue argument i.
That explains the difference you observe.
For the sake of experiment, in the latter case you can suppress template argument deduction and specify the template argument explicitly
succeed<int>(i);
That will forcefully specify T as int and lead to the very same error as in the class version for the very same reason.
Similarly, you can "simulate" function's behavior for your class by specifying the template argument as int &
test<int &> t(i);
The same "reference collapsing" rules will make your constructor invocation to compile successfully.
As far as I know, I cannot declare an rvalue reference to void.
As an example, the following code is ill-formed:
void f(void &&v) { }
From [20.2.6/1] (function template declval) we have a declaration for declval that is:
template <class T>
add_rvalue_reference_t<T>
declval() noexcept;
Thus, declval<void> (let me say) would result in void &&, that I guessed it was ill-formed as well as in the previous example.
Anyway, the following minimal, working example compiles:
#include<utility>
int main() {
decltype(std::declval<void>())* ptr = nullptr;
}
Note that the following is true too:
static_assert(std::is_same<decltype(std::declval<void>()), void>::value, "!");
I would have expected it to be void&& as previously mentioned (or better, I was expecting it fails to compile).
Actually, it happens to be an rvalue reference for any other non-reference type.
As an example:
static_assert(std::is_same<decltype(std::declval<int>()), int&&>::value, "!");
Is declval<void> a valid expression or not? Is the code above legal?
Why does the behavior in case of void is different than with any other type? (For it wouldn't have worked otherwise could be an answer, if the code is legal).
If it's legal, where does the standard allow that? I've not been able to find the case.
Of course, the standard says:
The template parameter T of declval may be an incomplete type.
Anyway, here it would result in a non acceptable type (void&&) and it works around it discarding the rvalue reference.
add_rvalue_reference<T> only results in T&& if T is a referenceable type. So when T is void, the result is just void. This is also why you can add_rvalue_reference<int&&> and not get an error attempting to construct a reference to a reference. (Same with lvalue reference.)
C++03 lets you qualify function parameters as being const, volatile, and/or lvalue references (&).
C++11 adds one more: rvalue references (&&).
Furthermore, C++ lets you overload functions based on the qualifiers of their parameters, so that the most appropriate overload is selected when calling the function.
A member function can conceptually be thought of as a function which takes an extra parameter, whose type is a reference to an instance of the class of which it is a member. It's possible to overload a member function based on the qualifiers of this 'extra parameter' in much the same way as any other parameter. This is expressed by putting the qualifiers at the end of the function signature:
struct Foo
{
int& data(); // return a non-const reference if `this` is non-const
const int& data() const; // return a const reference if `this` is const
};
In C++03, const and volatile qualifiers are possible, and C++11 also allows & and && (& could theoretically have been allowed in C++03, but it wasn't).
Any combination of qualifiers can be used, with the exception that & and && are mutually exclusive, which makes for 2^2 = 4 possibilities in C++03 and 2^4-4 = 12 in C++11.
This can be quite a pain when you want to work with member function pointers, because they aren't even a little bit polymorphic in these qualifiers: the qualifiers on the "this type" of a member function pointer passed as an argument must exactly match those on the type of the parameter it's being passed as. C++ also offers no explicit facility to abstract over qualifiers. In C++03 this was mostly OK, because you would have to write a const version and a non-const version and no one cares about volatile, but in the pathological case in C++11 (which is not as uncommon as it is pathological) you could have to manually write as many as 12 overloads. Per function.
I was very happy to discover that if you are passing the type of the enclosing class as a template parameter and derive the type of a member function pointer from it, that const and volatile qualifiers are allowed and propagated as you would expect:
template<typename Object>
struct Bar
{
typedef int (Object::*Sig)(int);
};
Bar<Baz>; // Sig will be `int (Baz::*)(int)`
Bar<const Baz>; // Sig will be `int (Baz::*)(int) const`
Bar<volatile Baz>; // Sig will be `int (Baz::*)(int) volatile`
Bar<const volatile Baz>; // Sig will be `int (Baz::*)(int) const volatile`
This is a great deal nicer than having to write out all of the cases manually.
Unfortunately, it doesn't seem to work for & and &&.
GCC 4.7 says:
error: forming pointer to reference type ‘Baz&&’
But that's not too surprising, given that GCC as of 4.7 doesn't yet have support for reference qualifiers on this.
I also tried it with Clang 3.0, which does have such support:
error: member pointer refers into non-class type 'Baz &&'
Oh, well.
Am I correct in concluding that this is not possible, and that there's no way to abstract over reference qualifiers on the "this type" of member function pointers? Any other techniques for abstracting over qualifiers (especially on this) other than in the specific case when you're passing the "this type" as a template parameter would also be appreciated.
(It's worth pointing out that if C++ didn't distinguish between member functions and normal functions, this would all be trivial: you'd use the template parameter as the type of a parameter of the function (pointer), and the template argument would be passed through as-is, qualifiers intact, no extra thought necessary.)
Have you thought about simply specializing your template ?
You can just add the two versions:
template <typename Object>
struct Bar<Object&> {
typedef int (Object::*Sig)(int)&;
};
template <typename Object>
struct Bar<Object&&> {
typedef int (Object::*Sig)(int)&&;
};
And then the compiler will pick the right specialization (or fallback to the general case) appropriately.
This saves you from the const/volatile thing, but does imply that you need to write the code 3 times.
If I define a function which accepts an rvalue reference parameter:
template <typename T>
void fooT(T &&x) {}
I can call it, using GCC 4.5, with either a, ar, or arr:
int a, &ar = a, &&arr = 7;
fooT(a); fooT(ar); fooT(arr);
However, calling a similar, non-template function,
void fooInt(int &&x) {}
with any of those three arguments will fail. I was preparing to strengthen my knowledge of forward, but this has knocked me off course. Perhaps it's GCC 4.5; I was surprised to find that the first example from A Brief Introduction to Rvalue References also gives a compile error:
A a;
A&& a_ref2 = a; // an rvalue reference
The behavior of deduction in template parameters is unique, and is the reason your template version works. I've explained exactly how this deduction works here, in the context of another question.
Summarized: when the argument is an lvalue, T is deduced to T&, and T& && collapses to T&. And with the parameter at T&, it is perfectly valid to supply an lvalue T to it. Otherwise, T remains T, and the parameter is T&&, which accepts rvalues arguments.
Contrarily, int&& is always int&& (no template deduction rules to coerce it to something else), and can only bind to rvalues.
In addition to GMan's correct answer A Brief Introduction to Rvalue References has an incorrect example because it was written prior to a language change which outlawed:
A a;
A&& a_ref2 = a; // an rvalue reference (DISALLOWED in C++11)
Despite this change in the language, the main uses cases described in the article (move and forward) are still explained correctly in the article.
Update: Oh, and the same article was originally published here with (imho) slightly better formatting.