3

I have following code:

#include <type_traits>

struct TType
{
    int a = 0;

    bool operator() (int&)
    {
        return true;
    }
};

int main()
{
    static_assert(std::is_same<decltype(std::declval<TType>()(std::declval<int>())), bool>::value, "wtf?");
    return 0;
}

If I try to compile it with g++-4.8.2 then I receive an error:

main.cpp:321:82: error: no match for call to ‘(JetPlane) (int)’
static_assert(std::is_same<decltype(std::declval<JetPlane>()(std::declval<int>())), bool>::value, "wtf?");
                                                                                ^
main.cpp:265:8: note: candidate is:
struct JetPlane
       ^
main.cpp:279:7: note: bool JetPlane::operator()(int&)
bool operator() (int&)
     ^
main.cpp:279:7: note:   no known conversion for argument 1 from ‘int’ to ‘int&’

I don't understand note: no known conversion for argument 1 from ‘int’ to ‘int&’ line. So the question is: why g++ interprets return type of std::declval<int>() like int and not line int&& though std::declval declaration looks like:

template< class T >
typename std::add_rvalue_reference<T>::type declval();

I understand that it's prohibited to bind int&& to int. But why then compiler doesn't print: note: no known conversion for argument 1 from ‘int’ to ‘int&’ line. May be I don't understand something and compiler changes somehow return type of std::declval<int>() from int&& on int in std::declval<TType>()(std::declval<int>())?

Thank you for help!

rbtrht
  • 219
  • 2
  • 6
  • `int&&` can't bind to `int&`. – Simple Jun 20 '14 at 09:58
  • Yes, I understand this. But why then compiler doesn't print: no known conversion for argument 1 from ‘int&&’ to ‘int&’ – rbtrht Jun 20 '14 at 09:59
  • 2
    I'd guess it's for legacy reasons. It's an rvalue of type `int`, and that has for ages meant the type is `int`, so that's what the error is reporting. Yes, in your case the type is `int&&`, but that's still an `int` rvalue. – Angew is no longer proud of SO Jun 20 '14 at 10:07

2 Answers2

5

The problem is, that you cannot bind an xvalue to a non-const lvalue reference.

Let's look at the expression

std::declval<int>()

The return type of std::declval<int> is indeed int&&. Hence the above expression is an xvalue expression of type int. Note that in C++ an expression never has a reference type.

But your operator

bool operator() (int&)

takes its argument by non-const lvalue referenece. If you change the perameter type to const int&, i.e.

bool operator() (const int&)

everything should work fine.

MWid
  • 4,429
  • 3
  • 20
  • 20
  • Thank you. That is I suspected I don't know: "Note that in C++ an expression never has a reference type." Good explanation of this [here](http://stackoverflow.com/questions/17241614/in-c-what-expressions-yield-a-reference-type-when-decltype-is-applied-to-them) – rbtrht Jun 20 '14 at 11:00
0

You cannot use an rvalue reference to initialize an lvalue reference. The confusing bit is that T&& means different things in different contexts:

  • when the T&& is used to define an object, the rvalue reference can be bound to an rvalue but the defined object is actually an lvalue which can bound to an lvalue reference
  • when the T&& is used to declare the return type of a function the meaning is that rvalue reference is returned which behaves for all purposes like a temporary and, thus, cannot be bound to an lvalue reference. The type T&& is treated like a temporary T for all purposes of overload resolution and it seems this is what the compiler reports.
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • 2
    OP asked why `std::declval()` is `int`, not `int&&`. – ikh Jun 20 '14 at 10:09
  • Why type `T&&` is treated like `T` if these are returning types of function? In my opinion it's incorrect because if `T&&` is returning type then function returns `xvalue` and if `T` is returing type then function returns `prvalue`. – rbtrht Jun 20 '14 at 10:20
  • @ikh: Well, it isn't. It just happens to be treated as if it is in the error message and I don't think you can detect the difference, certainly not for `int`. For an arbitrary type you may be able to detect the difference based on the move constructor being called (although one of them is likely to be elided). – Dietmar Kühl Jun 20 '14 at 11:35