Search code examples
c++templatesc++14autodecltype

prototype mismatch with decltype and auto


Consider the following class:

class MyClass
{
    int _id;
public:
    decltype(_id) getId();
};

decltype(MyClass::_id) MyClass::getId()
{
    return _id;
}

It compiles fine.

However when I make a template class out of it:

template <class T>
class MyClass
{
    int _id;
public:
    decltype(_id) getId();
};

template <class T>
decltype(MyClass<T>::_id) MyClass<T>::getId()
{
    return _id;
}

I get:

test.cpp:10:27: error: prototype for 'decltype (MyClass<T>::_id) MyClass<T>::getId()' does not match any in class 'MyClass<T>'
 decltype(MyClass<T>::_id) MyClass<T>::getId()                                                                                
                           ^
test.cpp:6:19: error: candidate is: decltype (((MyClass<T>*)(void)0)->MyClass<T>::_id) MyClass<T>::getId()
     decltype(_id) getId();
                   ^

Why is that?
Why the different types

  • decltype (MyClass<T>::_id) MyClass<T>::getId()
  • decltype (((MyClass<T>*)(void)0)->MyClass<T>::_id)

I could fix it by defining the body in the class:

template <class T>
class MyClass
{
    int _id;
public:
    decltype(_id) getId() { return _id; }
};

Trailing return type suffers a similar problem:

template <class T>
class MyClass
{
    int _id;
public:
    auto getId() -> decltype(_id);
};

template <class T>
auto MyClass<T>::getId() -> decltype(MyClass<T>::_id)
{
    return _id;
}

error:

test.cpp:10:6: error: prototype for 'decltype (MyClass<T>::_id) MyClass<T>::getId()' does not match any in class 'MyClass<T>'
 auto MyClass<T>::getId() -> decltype(MyClass<T>::_id)
      ^
test.cpp:6:10: error: candidate is: decltype (((MyClass<T>*)this)->MyClass<T>::_id) MyClass<T>::getId()
     auto getId() -> decltype(_id);
          ^
  • decltype (MyClass<T>::_id) MyClass<T>::getId()
  • decltype (((MyClass<T>*)this)->MyClass<T>::_id) MyClass<T>::getId()

g++ 5.3.0


Solution

  • According to the draft standard N4582 §5.1.1/p13 General [expr.prim.general] (Emphasis Mine):

    An id-expression that denotes a non-static data member or non-static member function of a class can only be used:

    (13.1) — as part of a class member access (5.2.5) in which the object expression refers to the member’s class63 or a class derived from that class, or

    (13.2) — to form a pointer to member (5.3.1), or

    (13.3) — if that id-expression denotes a non-static data member and it appears in an unevaluated operand. [Example:

    struct S {
    int m;
    };
    int i = sizeof(S::m); // OK
    int j = sizeof(S::m + 42); // OK
    

    — end example ]

    63) This also applies when the object expression is an implicit (*this) (9.3.1).

    Also from §7.1.6.2/p4 Simple type specifiers [dcl.type.simple](Emphasis Mine):

    For an expression e, the type denoted by decltype(e) is defined as follows:

    (4.1) — if e is an unparenthesized id-expression or an unparenthesized class member access (5.2.5), decltype(e) is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;

    (4.2) — otherwise, if e is an xvalue, decltype(e) is T&&, where T is the type of e;

    (4.3) — otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;

    (4.4) — otherwise, decltype(e) is the type of e.

    The operand of the decltype specifier is an unevaluated operand (Clause 5).

    [Example:

    const int&& foo();
    int i;
    struct A { double x; };
    const A* a = new A();
    decltype(foo()) x1 = 17; // type is const int&&
    decltype(i) x2; // type is int
    decltype(a->x) x3; // type is double
    decltype((a->x)) x4 = x3; // type is const double&
    

    — end example ] [ Note: The rules for determining types involving decltype(auto) are specified in 7.1.6.4. — end note ]

    Consequently, since decltype is an unevaluated operand the code is legitimate and should compile.

    One clean workaround would be to use decltype(auto):

    template<typename T>
    class MyClass {
      int _id;  
    public:
      decltype(auto) getId();
    };
    
    template<typename T>
    decltype(auto) MyClass<T>::getId() {
      return _id;
    }
    

    Above code is accepted by GCC/CLANG/VC++.