Правильное распространение переменной `decltype (auto)` из функции

(Это продолжение « Существуют ли какие-либо реалистичные варианты использования переменных` decltype (auto) `? » )

Рассмотрим следующее сценарий - я хочу передать функцию f другой функции invoke_log_return, которая будет:

  1. вызывать f;

  2. печатать что-то для stdout ;

  3. Возвращать результат f, избегая ненужных копий / перемещений и допуская копирование.

Обратите внимание, что, если f выбрасывает, ничего не должно быть напечатано на stdout . Вот что у меня есть:

template 
decltype(auto) invoke_log_return(F&& f)
{
    decltype(auto) result{std::forward(f)()};
    std::printf("    ...logging here...\n");

    if constexpr(std::is_reference_v)
    {
        return decltype(result)(result);
    }
    else
    {
        return result;
    }
}

Давайте рассмотрим различные возможности:

  • Когда f возвращает значение :

    • result будет объектом;

    • invoke_log_return(f) будет prvalue (имеет право на удаление копии) . [тысяча сто шестьдесят одна]

  • Когда f возвращает значение lvalue или xvalue :

    • result будет быть ссылкой;

      [1185]
    • invoke_log_return(f) будет lvalue или xvalue .

Вы можете увидеть тестовое приложение здесь, на godbolt.org . Как видите, g++ выполняет NRVO для случая prvalue , в то время как clang++ нет.

Вопросы:

  • Является ли это кратчайшим способом «идеального» возврата переменной decltype(auto) из функции? Есть ли более простой способ достичь того, чего я хочу?

  • Можно ли извлечь шаблон if constexpr { ... } else { ... } в отдельную функцию? Кажется, что единственный способ извлечь его - макрос.

    [+1149]
  • Есть ли веская причина, по которой clang++ не выполняет NRVO для случая prvalue , описанного выше? Следует ли сообщать об этом как о потенциальном улучшении, или это g++ ] Оптимизация NRVO здесь недопустима?


Вот альтернатива с использованием помощника on_scope_success (как предложил Барри Ревзин):

template 
struct on_scope_success : F
{
    int _uncaught{std::uncaught_exceptions()};

    on_scope_success(F&& f) : F{std::forward(f)} { }

    ~on_scope_success()
    {
        if(_uncaught == std::uncaught_exceptions()) {
            (*this)();
        }
    }
};

template 
decltype(auto) invoke_log_return_scope(F&& f)
{
    on_scope_success _{[]{ std::printf("    ...logging here...\n"); }};
    return std::forward(f)();
}

Хотя invoke_log_return_scope намного короче, для этого требуется другая ментальная модель поведения функции и реализация новой абстракции. Удивительно, но и g++, и clang++ выполняют RVO / copy-elision с этим решением.

живой пример на godbolt.org

Одним из основных недостатков этого подхода, как упомянуто Беном Фойгтом , является то, что возвращение значение f не может быть частью сообщения журнала.

21
задан Vittorio Romeo 13 August 2019 в 10:35
поделиться