Есть ли простой способ передать результат вызываемого объекта, когда он возвращает void?

Вот простой синтетический пример: https://godbolt.org/z/MKWh9a464

#include <iostream>

template <typename ... Args>
void foo(Args ...){
    std::cout << sizeof...(Args) << '\n';
}

void foo(void){
    std::cout << "void\n";
}

template <typename Callable>
void bar(Callable c){
    foo(c());
}

int main() 
{
    bar([](){
        return 42;
    });

    bar([](){});

    return 0;
}

Это приводит к ошибке:

<source>: In instantiation of 'void bar(Callable) [with Callable = main()::<lambda()>]':
<source>:23:8:   required from here
<source>:14:10: error: invalid use of void expression
   14 |     foo(c());
      |         ~^~
ASM generation compiler returned: 1
<source>: In instantiation of 'void bar(Callable) [with Callable = main()::<lambda()>]':
<source>:23:8:   required from here
<source>:14:10: error: invalid use of void expression
   14 |     foo(c());
      |         ~^~

Я написал немного SFINAE soultion, но он какой-то некрасивый: https://godbolt.org/z/4r9b4h34r


#include <type_traits>
#include <utility>

struct void_result{};

template <typename Result>
struct _get_result
{
    template <typename Callable, typename ... Args>
    constexpr decltype(auto) operator()(Callable c, Args &&... args) const noexcept {
        return c(std::forward<Args>(args)...);
    }
};

template <>
struct _get_result<void>
{
    template <typename Callable, typename ... Args>
    constexpr void_result operator()(Callable c, Args &&... args) const noexcept {
        c(std::forward<Args>(args)...);
        return {};
    }
};

template <typename Callable, typename ... Args>
constexpr decltype(auto) voidable(Callable c, Args && ... args) noexcept {
    return _get_result<std::invoke_result_t<Callable, Args...>>{}(c, std::forward<Args>(args)...);
}


#include <iostream>

template <typename ... Args>
void foo(Args ...){
    std::cout << sizeof...(Args) << '\n';
}

void foo(void_result){
    std::cout << "void\n";
}

template <typename Callable, typename ... Args>
void bar(Callable c, Args && ...args){
    foo(voidable(c, std::forward<Args>(args)...));
}

int main() 
{
    bar([](){
        return 42;
    });

    bar([](){});

    return 0;
}

Есть ли более простой способ передать результат, когда он void?

Формулировка вопроса кажется неуклюжей. Невозможно «передать результат, когда он void», потому что void не является результатом; это означает отсутствие результата. Его вообще нельзя «пройти»; учтите foo(foo()).

Karl Knechtel 31.01.2023 19:12

@KarlKnechtel, это подпроблема, возникшая при написании цепочки выполнения. То есть результат первого вызываемого объекта применяется к следующему вызываемому объекту, выполнение которого запланировано после первого. Например: doA() -> doB(), что означает, что doB должен вызываться с результатом doA(), даже если он возвращает void. Конечно, я не хочу, чтобы пользователи возвращали какие-то void_tag, но все же нужно иметь возможность связывать операции, которые возвращают void

Sergey Kolesnik 31.01.2023 19:36

Я понял это, но я пытаюсь придумать лучший способ сформулировать это в тексте вопроса.

Karl Knechtel 31.01.2023 19:51
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
3
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы можете использовать такой constexpr, я позволяю захвату лямбда выполнять захват аргументов (демонстрация: https://onlinegdb.com/LK2fzamlX)

#include <iostream>

template<typename fn_t>
auto meta_function(fn_t fn) -> decltype(fn())
{
    using retval_t = decltype(fn());

    if constexpr (std::is_same_v<void, retval_t>)
    {
        std::cout << "void code\n";
        return;
    }
    else
    {
        retval_t retval = fn();
        std::cout << "retval inside meta_function = " << retval << "\n";
        return retval;
    }
}

int main()
{
    auto retval = meta_function([] {return 42; });
    std::cout << "retval returned from meta_function = " << retval << "\n";
    meta_function([] {});
    return 0;
}

1) Это предотвращает удаление копии retval 2) Мне нужно вызвать соответствующую перегрузку foo() - она ​​либо принимает любые аргументы, либо принимает «нет». Эти перегрузки не могут быть жестко закодированы

Sergey Kolesnik 31.01.2023 18:56

Затем измените ветки if-else на fn(); foo(); и fn(foo()); соответственно?

HolyBlackCat 31.01.2023 19:07

@HolyBlackCat, ты прав. И для моего конкретного случая этого будет достаточно. Я думаю, что до C++17 нужно придерживаться SFINAE.

Sergey Kolesnik 31.01.2023 20:54

Другие вопросы по теме