VC++ Bug with Decltype and Universal Reference or Undefined Behavior?

144 views Asked by At

I have this code:

#include <iostream>
#include <type_traits>
using namespace std;
template<typename T>
T f2(T&&t) {
    return t;
}
int rr(int i) {
    return 40*i;
}
int main()
{
  cout << is_function< remove_reference<decltype(f2(rr))>::type>::value << endl;
}

When compiled with VC++2015, i got this error:

error C2893: Failed to specialize function template 'T f2(T &&)'

The main issue is with using decltype() on the expression f2(rr). Note that f2's parameter is T&&. This is called Universal Reference by Scott Meyers: universal reference. I am expecting f2(rr) yields an expression whose type is a function reference. In GCC, it runs normally and returns true, and hence, confirms that f2(rr) is a function reference.

Is this simply a bug with VC++2015 rather than undefined behavior when the univeral reference is used with a function name?

EDIT: This works correctly in VC++2015:

int main()
{
  cout << f2(rr)(10) <<endl;
}

result:

400
1

There are 1 answers

0
AudioBubble On BEST ANSWER

Universal references are the special context where a template parameter can be deduced as a reference type. Suppose we have

template <typename T> auto f(T &&) -> T;
int i;
int g(int);

f(i) deduces T as int & to form auto f<int &>(int &) -> int &.
f(std::move(i)) deduces T as int to form auto f<int>(int &&) -> int.

The rules for when this happens are given as:

14.8.2.1 Deducing template arguments from a function call [temp.deduct.call]

3 [...] If P is an rvalue reference to a cv-unqualified template parameter and the argument is an lvalue, the type "lvalue reference to A" is used in place of A for type deduction. [...]

The question is, when calling f(g), is g an lvalue?

Does it even make sense to ask the question? If T is a function type, then T & and T && can both be used to create references to named functions, without any need for std::move. The lvalue/rvalue distinction doesn't exist for functions.

Arbitrarily, the C++ standard says that yes, g is an lvalue. Expressions of function type are never rvalues (whether xvalues or prvalues). It's possible to produce types that are rvalue references to function types, but even then, the expressions formed by them are lvalues. There are more explicit statements elsewhere in the standard, but they follow from:

3.10 Lvalues and rvalues [basic.lval]

(1.1) -- An lvalue [...] designates a function or an object. [...]

(1.2) -- An xvalue [...] also refers to an object, [...]

(1.4) -- An rvalue [...] is an xvalue, a temporary object (12.2) or subobject thereof, or a value that is not associated with an object.

(Note: g is not a "value" either, so that last part doesn't apply. "Value" is defined in [basic.types]p4 and applies to trivially copyable types, which function types are not.)

Since g is an lvalue, T should be deduced as int(&)(int), and this is a bug in VC++. Quite a subtle one though.