Jive Dadson Jive Dadson - 20 days ago 9
C++ Question

Why does the compiler want a trailing return-type?

I was playing around with an adapter for using range-based for-loops to iterate in reverse. (I did not know about the boost adapter ("adaptor") for that purpose. I am a big believer in not re-inventing the wheel if it's a free wheel I have already downloaded.)

What puzzles me is why VC++ 2012 is not happy unless I use trailing return-types in the code that follows:

#include <string>
#include <iostream>

template<class Fwd>
struct Reverser {
const Fwd &fwd;
Reverser<Fwd>(const Fwd &fwd_): fwd(fwd_) {}
auto begin() -> decltype(fwd.rbegin()) const { return fwd.rbegin(); }
auto end() -> decltype(fwd.rend()) const { return fwd.rend(); }
};

template<class Fwd>
Reverser<Fwd> reverse(const Fwd &fwd) { return Reverser<Fwd>(fwd); }

int main() {
using namespace std;
const string str = ".dlrow olleH";
for(char c: reverse(str)) cout << c;
cout << endl;
}


When I tried the following, I got the errors, "error C2100: illegal indirection," and "error C2228: left of '.rbegin' must have class/struct/union". What am I missing?

template<class Fwd>
struct Reverser {
const Fwd &fwd;
Reverser<Fwd>(const Fwd &fwd_): fwd(fwd_) {}
decltype(fwd.rbegin()) begin() const { return fwd.rbegin(); }
decltype(fwd.rend()) end() const { return fwd.rend(); }
};


UPDATE: In light of the discussion about a "this" pointer, I tried another tack. Look Ma, no this! And it compiles fine. I do believe that, rightly or wrongly, VC++ is not aware of that this.

template<class Fwd>
struct Reverser {
const Fwd &fwd;
Reverser<Fwd>(const Fwd &fwd_): fwd(fwd_) {}
decltype(((const Fwd*)0)->rbegin()) begin() const { return fwd.rbegin(); }
decltype(((const Fwd*)0)->rend()) end() const { return fwd.rend(); }

};


UPDATE 2: Submitted to MS: https://connect.microsoft.com/VisualStudio/feedback/details/765455/vc-2012-compiler-refuses-decltype-return-spec-for-member-function

UPDATE 3: This problem is fixed as of VC++ 2015. Thanks, Microsoft person.

Answer

My original answer that the code that VC++ rejected is disallowed in the standard actually was wrong: It is allowed as Johannes Schaub pointed out: in 5.1 [expr.prim.general] paragraph 12 it is described where an id-expression denoting a non-static data member or a non-static member function can be used. In particular, the last bullet states:

if that id-expression denotes a non-static data member and it appears in an unevaluated operand.

The expression in decltype(expr) is an unevaluated operand. Furthermore, 9.3.1 [class.mfct.non-static] paragraph 3 explains the situation where this is implicitly added to an expression:

When an id-expression (5.1) that is not part of a class member access syntax (5.2.5) and not used to form a pointer to member (5.3.1) is used in a member of class X in a context where this can be used (5.1.1), if name lookup (3.4) resolves the name in the id-expression to a non-static non-type member of some class C, and if either the id-expression is potentially evaluated or C is X or a base class of X, the id-expression is transformed into a class member access expression (5.2.5) using (*this) (9.3.2) as the postfix-expression to the left of the . operator.

The context in question is not "potentially evaluated" and there is no based involved. Thus, this isn't added and doesn't have to be in scope. Taken together, this means that the declaration

decltype(fwd.rbegin()) begin() const;

should be legal. It seems that using

decltype(static_cast<Reverser<Fwd> const*>(0)->fwd.rbegin()) begin() const;

is a work-around in cases where the compiler isn't correctly implemented.