如何简洁传递返回void的C++可调用对象执行结果?
问题:传递返回void的可调用对象执行结果的简洁实现
在C++中,若尝试将返回void的可调用对象的执行结果直接传递给函数,会触发编译错误。以下是简化示例:
#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实现方案
你已实现了一套SFINAE方案,但代码较为繁琐:
#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; }
更简洁的实现方式
在C++17及以上版本中,可利用if constexpr结合std::invoke_result_t直接在逻辑内区分处理,无需复杂的模板特化结构:
方案1:调整foo重载,匹配无参调用
将原void foo(void)修改为更符合C++风格的无参形式,直接在bar内分支处理:
#include <iostream> #include <type_traits> template <typename ... Args> void foo(Args ...){ std::cout << sizeof...(Args) << '\n'; } void foo(){ std::cout << "void\n"; } template <typename Callable> void bar(Callable c){ if constexpr (std::is_void_v<std::invoke_result_t<Callable>>) { c(); foo(); } else { foo(c()); } } int main() { bar([](){ return 42; }); bar([](){}); return 0; }
方案2:保留原重载,简化包装逻辑
若需保留foo(void_result)的重载,可通过单个包装函数替代原有的模板特化:
#include <iostream> #include <type_traits> #include <utility> struct void_result{}; template <typename Callable, typename... Args> constexpr auto wrap_void(Callable&& c, Args&&... args) { if constexpr (std::is_void_v<std::invoke_result_t<Callable, Args...>>) { std::forward<Callable>(c)(std::forward<Args>(args)...); return void_result{}; } else { return std::forward<Callable>(c)(std::forward<Args>(args)...); } } 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(wrap_void(std::forward<Callable>(c), std::forward<Args>(args)...)); } int main() { bar([](){ return 42; }); bar([](){}); return 0; }
两种方案均通过编译期分支替代了繁琐的模板特化,代码更简洁直观。
内容的提问来源于stack exchange,提问作者Sergey Kolesnik
相关产品推荐
相关产品推荐

