decltype () для std :: move () захвата значения в лямбде приводит к неверному типу - PullRequest
6 голосов
/ 14 февраля 2020

Кажется, что либо с Clang (9.0.0) что-то не так, либо мое понимание того, как decltype() указано для работы в стандарте. Со ссылкой на следующий код,

#include <utility>
#include <string>

template <typename...> class WhichType;

template <typename T>
std::remove_reference_t<T>&& move_v2(T&& t) {
    WhichType<std::remove_reference_t<T>&&>{};
    return static_cast<std::remove_reference_t<T>&&>(t);
}

int main() {
    auto x = std::string{"a"};
    [v = x]() { 
        // move_v2(v);
        // WhichType<decltype(move_v2(v))>{};
        WhichType<decltype(std::move(v))>{};
    }();
}

Приведенный выше код имеет вывод компилятора implicit instantiation of undefined template 'WhichType<std::__1::basic_string<char> &&>' вместо ожидаемого const std::__1::basic_string<char> && в параметрах шаблона WhichType. Использование move_v2 или WhichType в move_v2 само по себе, похоже, дает правильную вещь.

Однако, похоже, Clang также разрешает перегрузку выражения std::move(v), как я и ожидал https://wandbox.org/permlink/Nv7yXnCbqxjJMVvX. Это отвлекает меня от некоторых забот go, но я до сих пор не понимаю поведение decltype() внутри лямбды.

G CC, по-видимому, не имеет этого несоответствия в данном конкретном случае https://wandbox.org/permlink/5mhrOzLn5XZO8LNB.


Может ли кто-нибудь поправить меня, если я ошибаюсь в моем понимании decltype(), или указать точные места, где эта ошибка проявляется в лязге? Кажется, немного страшно с первого взгляда. Это может вызвать проблемы при использовании в SFINAE или что-то подобное.

Ответы [ 2 ]

0 голосов
/ 15 февраля 2020

Clang не так. decltype(std::move(v)) должно быть const &&, потому что cv-квалификатор v (который эквивалентен this->v) является объединением cv-квалификаторов *this (который const в operator() не- mutable лямбда) и v (что не имеет значения), поэтому v является значением const l. Затем std::move преобразуется в значение x соответствующего типа, поэтому decltype должно быть const &&.

decltype ведет себя особенно, когда применяется к выражению id или выражению доступа к члену (this->v), но здесь дело не в этом. std::move(v) - сложное выражение, поэтому оно рассматривается как обычное выражение.

0 голосов
/ 14 февраля 2020

Я немного покопался, и мне кажется, что ответ немного более детальный, чем кажется из некоторых комментариев.

Сначала есть этот ответ на более ранний ответ. вопрос . Процитируем важную часть:

[C++11: 5.1.2/14]: Сущность захвачена копией , если она неявно захвачена и capture-default равен = или , если он явно захвачен захватом, который не включает &. Для каждого объекта, захваченного копией, в закрывающем типе объявляется неназванный элемент данных, не содержащий данных c. Порядок объявления этих членов не уточняется. Тип такого элемента данных - это тип соответствующего захваченного объекта, если объект не является ссылкой на объект, или ссылочный тип в противном случае. [..]

Тогда есть этот ответ на другой вопрос . Снова цитата:

5 Тип закрытия для лямбда-выражения не универсального c имеет открытый оператор вызова встроенной функции [...] Этот оператор вызова функции или шаблон оператора объявляется const (9.3.1) в том и только в том случае, если условие-объявления-выражения лямбда-выражения не сопровождается mutable.

I тогда соберите это вместе в небольшом тесте:

#include <iostream>
using std::cout;
using std::endl;

void foo(const std::string&) {
    cout << "void foo(const std::string&)" << endl;
}

void foo(std::string&) {
    cout << "void (std::string&)" << endl;
}

struct klaf
{
    std::string b;

    void bla() const
    {
        cout << std::boolalpha << std::is_const<decltype(b)>::value << endl;
        foo(b);
    }
};

int main()
{
    klaf k;
    k.bla();

    std::string s;
    const std::string s2;
    auto lam = [=]() {
        cout << std::boolalpha << std::is_const<decltype(s)>::value << endl;
        foo(s);

        cout << std::boolalpha << std::is_const<decltype(s2)>::value << endl;
        foo(s2);
    };
    lam();
}

Какие выходы (и выходы одинаковы в G CC, Clang и MSV C):

false
void foo(const std::string&)
false
void foo(const std::string&)
true
void foo(const std::string&)

klaf::b (очевидно) не const, но поскольку функция klaf::bla равна const, то klaf::b рассматривается как const при вызове foo.

То же самое Значение true в lam, где s фиксируется значением со значением std::string. Однако s2 был объявлен как const std::string, и это относится к типу элемента данных в лямбда-выражении.

Вкратце: захват по значению в лямбда-выражении не сделайте сам захваченный элемент const, но, поскольку operator() для лямбды равно const, тогда члены повышаются до const в этой функции (если лямбда не объявлена ​​изменяемой).

EDIT :

Вдохновленный комментариями @arnes, я обнаружил разницу в G CC и Clang:

int main()
{
    int i = 12;
    auto lam = [=, ic = i]() {
        cout << std::boolalpha << std::is_const<decltype(i)>::value << endl;
        cout << std::boolalpha << std::is_const<decltype(ic)>::value << endl;
    };
    lam();
}

Это дает false false в Clang, но false true в G CC. Другими словами, захват с инициализатором становится const в G CC, но не в Clang.

...