Как лучше всего протестировать и развернуть std :: необязательно в операторе if - PullRequest
2 голосов
/ 18 марта 2020

У меня есть несколько функций, которые возвращают std::optional<T>. Вот пример для вымышленного типа MyType:

struct MyType {
    // ...
}

std::optional<MyType> calculateOptional() {
    // ... lengthy calculation

    if (success) {
        return MyType(/* etc */);
    }

    return std::nullopt;
}

Предположим, что эти функции выполняются дорого, и я хочу не вызывать их более одного раза.

При вызове их Я хочу немедленно протестировать необязательное, и если оно содержит значение, я хочу использовать его немедленно и никогда больше. Например, в Swift я могу использовать стандартное выражение if-let:

if let result = calculateOptional() {
    // Use result var
}

Я хотел бы повторить это поведение test-and-unwrap в C ++, сохраняя при этом код максимально чистым на точка использования. Например, очевидное простое решение (по крайней мере для меня) было бы:

if (auto result = calculateOptional()) {
    MyType result_unwrapped = *result;
    // Use result_unwrapped var
}

Но вы должны развернуть внутри if или использовать *result везде, что вам не нужно делать со Swift.

Мое единственное решение, которое действительно близко приближается к внешнему виду и ощущениям Swift:

template<typename T> bool optionalTestUnwrap(std::optional<T> opt, T& value) {
    if (!opt.has_value()) { return false; }
    value = *opt;
    return true;
}

#define ifopt(var, opt) if (typename decltype((opt))::value_type (var); optionalTestUnwrap((opt), (var)))

ifopt (result, calculateOptional()) {
    // Use result var
}

... но я также не большой поклонник использование макроса для замены нормального оператора if.

Ответы [ 2 ]

3 голосов
/ 18 марта 2020

Лично я бы просто сделал:

if (auto result = calculateOptional()) {
    // use *result
}

со вторым лучшим вариантом - присвоить необязательному уродливому имени и сделать для него псевдоним с более хорошим названием:

if (auto resultOpt = calculateOptional()) {
    auto& result = *resultOpt;
    // use result
}

I думаю, что это достаточно хорошо. Это отличный пример использования для преднамеренного дублирования имени внешней области (т.е. именования optional и внутреннего псевдонима result), но я не думаю, что нам нужно go сумасшествие здесь. Даже использование *result не является большой проблемой - система типов, скорее всего, перехватит все злоупотребления.

Если мы действительно хотим включить go в Swift, используемый вами макрос требует построения по умолчанию - и в этом нет необходимости. Мы можем сделать немного лучше с (в идеале __opt заменяется механизмом, который выбирает уникальное имя, например, конкатенация с __LINE__):

#define if_let(name, expr)              \
    if (auto __opt = expr)              \
        if (auto& name = *__opt; false) {} else

Как в:

if_let(result, calculateOptional()) {
    // use result
} else {
    // we didn't get a result
}

Это не требует дополнительных затрат или требований. Но это немного смешно, имеет свои проблемы и не кажется стоящим. Но если мы просто веселимся, это работает.

0 голосов
/ 18 марта 2020

Вы можете заключить необязательный в собственный тип с неявным преобразованием в тип и явным в bool. Извините, я не проверял это до сих пор, но я думаю, что это должно работать.

template<class T>
struct opt {
    std::optional<T> _optional; // public so it stays an aggregate, probably writing constructors is better

    explicit bool() const {
        return _optional.has_value();
    }

    T&() {
        return *_optional;
    }

    const T&() const {
         return *_optional;
    }

    T&&() && { // Let's be fancy
         return std::move(*optional);
    }
}

opt<int> blub(bool val) {
    return val ? opt<int>{0} : opt<int>{std::nullopt};
}

int main() {
    if(auto x = blub(val)) { // I hope this works as I think it does
        int y = x+1;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...