Вот простой синтетический пример: 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
?
@KarlKnechtel, это подпроблема, возникшая при написании цепочки выполнения. То есть результат первого вызываемого объекта применяется к следующему вызываемому объекту, выполнение которого запланировано после первого. Например: doA() -> doB()
, что означает, что doB
должен вызываться с результатом doA()
, даже если он возвращает void
. Конечно, я не хочу, чтобы пользователи возвращали какие-то void_tag
, но все же нужно иметь возможность связывать операции, которые возвращают void
Я понял это, но я пытаюсь придумать лучший способ сформулировать это в тексте вопроса.
Вы можете использовать такой 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()
- она либо принимает любые аргументы, либо принимает «нет». Эти перегрузки не могут быть жестко закодированы
Затем измените ветки if-else на fn(); foo();
и fn(foo());
соответственно?
@HolyBlackCat, ты прав. И для моего конкретного случая этого будет достаточно. Я думаю, что до C++17 нужно придерживаться SFINAE.
Формулировка вопроса кажется неуклюжей. Невозможно «передать результат, когда он
void
», потому чтоvoid
не является результатом; это означает отсутствие результата. Его вообще нельзя «пройти»; учтитеfoo(foo())
.