Страницы

Поиск по вопросам

воскресенье, 29 декабря 2019 г.

Определение типа захватываемой лямбдой переменной

#cpp #lambda #language_lawyer


#include 
#include 


int main() {
    int x, &y = x;
    [=] {
        std::cout << std::is_same_v
                  << std::is_same_v
                  << std::is_same_v;

        std::cout << std::is_same_v
                  << std::is_same_v
                  << std::is_same_v;
    }();
}


Компилятор gcc выводит 001010, а clang - 001001. Какой вариант правильный и почему?
    


Ответы

Ответ 1



Достаточно запутанный вопрос, но похоже что clang прав. Для начала рассмотрим, как определяется тип выражения e в decltype(e). dcl.type.decltype#1: For an expression e, the type denoted by decltype(e) is defined as follows: if e is an unparenthesized id-expression naming a structured binding, decltype(e) is the referenced type as given in the specification of the structured binding declaration; otherwise, if e is an unparenthesized id-expression naming a non-type template-parameter ([temp.param]), decltype(e) is the type of the template-parameter after performing any necessary type deduction ([dcl.spec.auto], [dcl.type.class.deduct]); otherwise, if e is an unparenthesized id-expression or an unparenthesized class member access, 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; otherwise, if e is an xvalue, decltype(e) is T&&, where T is the type of e; otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e; otherwise, decltype(e) is the type of e. x и y являются glvalue, но не являются xvalue, а значит являются lvalue в соответствии с basic.lval#fig:categories: Таким образом, срабатывает предпоследний пункт определения типа, то есть варианты с int отпадают сразу. Далее, рассмотрим следующую цитату. expr.prim.lambda.capture#11: Every id-expression within the compound-statement of a lambda-expression that is an odr-use of an entity captured by copy is transformed into an access to the corresponding unnamed data member of the closure type. [ Note: An id-expression that is not an odr-use refers to the original entity, never to a member of the closure type. However, such an id-expression can still cause the implicit capture of the entity. — end note ] ... В данном случае выражения не являются потенциально вычисляемыми (т.к. являются операндами decltype), а значит соответствующие переменные не являются odr-использованными. Однако важно примечание - такие выражения всё ещё могут рассматриваться в контексте лямбда-замыканий, что подтверждается следующей цитатой. expr.prim.id.unqual#2: ... If the entity is a local entity and naming it from outside of an unevaluated operand within the declarative region where the unqualified-id appears would result in some intervening lambda-expression capturing it by copy ([expr.prim.lambda.capture]), the type of the expression is the type of a class member access expression ([expr.ref]) naming the non-static data member that would be declared for such a capture in the closure object of the innermost such intervening lambda-expression. [ Note: If that lambda-expression is not declared mutable, the type of such an identifier will typically be const qualified. — end note ] ... Другими словами, несмотря на то что захвата не происходит, тип выражения определяется так, как будто бы захват есть. Также в примечании сказано про константность идентификатора, если лямбда не имеет спецификатора mutable, что подтверждается следующим пунктом. expr.prim.labmda.closure#4: The function call operator or operator template is declared const ([class.mfct.non-static]) if and only if the lambda-expression's parameter-declaration-clause is not followed by mutable. ... Интересно, что из-за нечёткости формулировок и разбросанности их по разным разделам стандарта (на мой взгляд) оба компилятора имеют соответствующие багрепорты: gcc, clang. Однако в clang репорт закрыт как неверный (RESOLVED INVALID), а в gcc открыт до сих пор (UNCONFIRMED).

Ответ 2



При захвате по значению [=] переменные захватываются копированием. Для каждой захваченной сущности создаётся безымянный член данных внутри лямбда объекта. Для ссылок на объекты тип получается тоже ссылочный: The type of such a data member is the referenced type if the entity is a reference to an object, an lvalue reference to the referenced function type if the entity is a reference to a function, or the type of the corresponding captured entity otherwise. Так как уточнения о том, какой должна быть ссылка: const или не-const нет, оба рассмотренных компилятора дают подходящий под стандарт результат. Однако, известно, что для модификации, захваченных по значению сущностей нужно дополнительно помечать лямбду как mutable. И если это сделать, можно увидеть, что оба компилятора уже будут давать одинаковые результаты (clang, gcc): #include #include int main() { int x = 42, &y = x; [=]() mutable { std::cout << std::is_same_v << std::is_same_v << std::is_same_v; x = 0; y = 100500; }(); std::cout << "\n" << x << "\n"; } 010 42 Так как требуется явная модификация y = 100500, то тип y уже не может быть константной ссылкой и становится обычной ссылкой. Модифицируются же по-прежнему копии внутри лямбды.

Ответ 3



Ваш пример практически повторяет пример из C++17 стандарта языка. void f3() { float x, &r = x; [=] { // x and r are not captured (appearance in a decltype operand is not an odr-use) decltype(x) y1; // y1 has type float decltype((x)) y2 = y1; // y2 has type float const& because this lambda is not mutable and x is an lvalue decltype(r) r1 = y1; // r1 has type float& (transformation not considered) decltype((r)) r2 = y2; // r2 has type float const& }; } Форма decltype((x)) не является odr-use для x, т.е. она не вызывает неявного захвата x, но она должна вести себя так, как будто x было захвачено и decltype((x)) ссылается именно на захваченное x. При захвате ссылки по значению происходит захват по значению именно ссылаемого объекта. Таким образом никакой разницы в способах x и y захвата в вашем примере нет. Ваши захваченные x и y имеют тип int. Внутри тела не-mutable лямбды они видны как lvalues типа const int. Т.е. в контексте тела вашей лямбды и decltype((x)) и decltype((y)) дают тип const int &. Однако эта часть стандарта подвергается переработке для C++20. Возможно, что есть изменения.

Комментариев нет:

Отправить комментарий