GCC не может захватить указатель 'this' на шаблонный тип, используя init-capture

Шаблонный класс может захватить свой собственный указатель this в lambda:

template <typename T>
class Foo {
  public:
    void foo(void) {}
    auto getCallableFoo(void) {
      return [this]() { this->foo(); };
    }
};

Этот и все другие примеры Foo могут быть протестированы с использованием следующего кода:

int main()
{
  Foo<int> f;
  auto callable = f.getCallableFoo();
  callable();
}

Однако, если вместо этого используется init-capture, это больше не работает с GCC:

    auto getCallableFoo(void) {
      return [ptr = this]() { ptr->foo(); };
    }

Сообщение об ошибке (из GCC 5.1):

error: ‘Foo<T>::getCallableFoo()::<lambda()>::__ptr’ has incomplete type

Clang 3.7, похоже, компилирует и запускает этот код без ошибок. (Я фактически использую версию, скомпилированную из исходного кода до версии 3.7, но я не ожидаю, что это сломается с тех пор.)

Предполагаемый захват должен вести себя как присвоение auto, но следующий код работает без ошибок в GCC:

// New method in Foo:
auto getPtr(void) {
  return this;
}

// Usage:
auto ptr = f.getPtr();
ptr->foo();

Так почему же значение ptr не может захватить this в GCC? Это ошибка?

Еще одно соображение состоит в том, что в соответствии с CppReference, this рассматривается как отдельный синтаксический случай из любого другого типа списка захвата. Таким образом, это может быть одним из намеков на то, почему GCC рассматривает эти случаи по-разному. Но мне непонятно, какая (если таковая имеется) специальная обработка для этого особого случая или почему это вообще особый случай.

EDIT: Похоже, что это работает:

return [ptr = static_cast<decltype(this)>(this)]() { ptr->foo(); };

Это не имеет для меня никакого смысла, потому что decltype (в отличие от auto) точно указывает тип своего аргумента, поэтому static_cast не должен влиять ни на что.

EDITS 2,3,4: Здесь приведен полный список выражений, которые я пробовал с обоими компиляторами, с комментариями, указывающими, какой компилятор принимает каждое выражение:

[this]() { this->foo(); };        // Both: work
[ptr = this]() { ptr->foo(); };   // GCC fails
[ptr = static_cast<decltype(this)>(this)]() { ptr->foo(); };   // Both: works (!!!)
[ptr(this)]() { ptr->foo(); };   // GCC fails
[ptr{this}]() { ptr->foo(); };   // GCC works (!!!!!!!!), Clang doesn't work (infers initializer list)
[ptr = {this}]() { ptr->foo(); };   // Both: fail (infers initializer list)
[ptr = &*this]() { ptr->foo(); };  // Both: work
[ptr = &*(this)]() { ptr->foo(); };  // Both: work

Для [ptr{this}] моя версия Clang (предварительная версия 3.7) предупреждает, что интерпретация изменится; в настоящее время он отображает список инициализаторов, но предположительно более поздние версии (или уже делают) выводят тип this в соответствии с новыми правилами auto из N3922.

Меня шокирует, что GCC разрешает [ptr{this}], но не [ptr(this)]. У меня нет объяснений.

Ответ 1

Это ошибка.

Это ошибка. Я отправил отчет об ошибке GCC для этой проблемы. В настоящее время зафиксировано в соединительной линии GCC.

Обход

Как отмечено Revolver_Ocelot, &* появляется, чтобы заставить g++ выполнить правильный вывод типа. Таким образом, мое текущее обходное решение (которое внутри макроса, принимающее какое-то выражение указателя, которое может быть this), должно захватывать [ptr = &*(ptr_expr)].

Почему это произошло?

Как отмечалось выше, GCC Джейсон Меррилл зафиксировал это в стволе GCC. Он замечает, что указатель this требует специальной обработки в лямбда-захватах; в частности, он рассматривается как не зависимый. Ранее эта специальная обработка применялась к [this], но не к [ptr = this].