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]
.