Несоответствие прототипа с помощью decltype и auto

Рассмотрим следующий класс:

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

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

Он компилируется отлично.

Однако, когда я делаю из него класс шаблона:

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

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

Я получаю:

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();
                   ^

Почему это? Почему разные типы

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

Я мог бы исправить это, определив тело в классе:

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

Обратный тип возвращаемого типа испытывает аналогичную проблему:

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;
}

Ошибка:

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

Ответы

Ответ 1

В соответствии с проектом стандарта N4582 §5.1.1/p13 Общие сведения [expr.prim.general] (Акцент Mine):

Идентификатор, который обозначает нестатический элемент данных или нестатический функция члена класса может использоваться только:

(13.1) - как часть доступа члена класса (5.2.5), в котором объект выражение относится к классу членов 63 или классу полученный из этого класса, или

(13.2) - для формирования указателя на элемент (5.3.1) или

(13.3) - если это id-выражение обозначает нестатический элемент данных, и это появляется в неопубликованном операнде. [Пример:

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

- конец примера]

63) Это также применяется, когда выражение объекта является неявным (* this) (9.3.1).

Также из §7.1.6.2/p4 Спецификаторы простого типа [dcl.type.simple] (Акцент Mine):

Для выражения e тип, обозначенный символом decltype(e), определяется как следующим образом:

(4.1) - если e - это неравномерное id-выражение или unparenthesized член класса (5.2.5), decltype(e) - тип объекта названный e. Если такой объект отсутствует или если e называет набор перегруженные функции, программа плохо сформирована;

(4.2) - в противном случае, если e - значение x, decltype(e) - T&&, где T - тип e;

(4.3) - в противном случае, если e является l значением, decltype(e) является T&, где Tявляется типом e;

(4.4) - в противном случае decltype(e) является типом e.

Операндом спецификатора decltype является неоцениваемый операнд (раздел 5).

[Пример:

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&

- конец примера] [Примечание: правила определения типов, включающие decltype (auto) указаны в 7.1.6.4. - конечная нота]

Следовательно, поскольку decltype является неоцененным операндом, код является законным и должен компилироваться.

Одним из простых способов решения проблемы будет использование decltype(auto):

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

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

Выше код принят GCC/CLANG/VС++.

Ответ 2

Кажется, что это ошибка g++.

Я пробовал свой код в Visual Studio 2015:

Сборка: 1 успешно, 0 не удалось, 0 обновлено, 0 пропущено

Изменить: я нашел обходной путь:

#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
}

Вывод:

0

Ответ 3

Кажется, что GCC Ошибка 57712.

Пример кода из описания ошибки:

struct Test {
  int method(int value) { return value; }

  template <typename T>
  auto test(T value) -> decltype(this->method(value));
};

template <typename T>
auto Test::test(T value) -> decltype(this->method(value)) {
  return this->method(value);
}