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
'auto' lets you declare a variable with a particular type whereas decltype lets you extract the type from the variable so decltype is sort of an operator that evaluates the type of passed expression.
Use auto and decltype to declare a function template whose return type depends on the types of its template arguments. Or, use auto and decltype to declare a function template that wraps a call to another function, and then returns the return type of the wrapped function.
In C++11, you can use the decltype type specifier on a trailing return type, together with the auto keyword, to declare a template function whose return type depends on the types of its template arguments.
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 bydecltype(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)
isT&&
, whereT
is the type ofe
;(4.3) — otherwise, if e is an lvalue,
decltype(e)
isT&
, whereT
is the type ofe
;(4.4) — otherwise,
decltype(e)
is the type ofe
.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++.
It seems like this is g++
bug.
I've tried your code in Visual Studio 2015:
Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped
Edit: I found workaround:
#include <iostream>
template <class T>
class MyClass
{
T _id = {0};
public:
decltype(((MyClass<T>*)nullptr)->_id) getId();
};
template <class T>
decltype(((MyClass<T>*)nullptr)->_id) MyClass<T>::getId()
{
return _id;
}
int main()
{
MyClass<int> f;
auto n = f.getId();
std::cout << n << '\n'; // output: 0
}
Output:
0
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With