如何通过工厂对象与可变参数就地初始化std::variant?
问题:通过工厂对象就地构造std::variant并解决编译错误
问题背景
std::variant提供了接受std::in_place_t<Type>, Args &&...的构造函数,可实现目标类型的就地构造,示例如下:
{ std::cout << "in_place_type:\n"; const auto var = std::variant<callable>(std::in_place_type<callable>); std::cout << "finished\n"; }
尝试通过工厂函数实现就地构造时,直接调用工厂生成对象的方式会触发std::variant(T &&t)构造函数,无法利用拷贝消除,无法实现真正的就地构造:
{ std::cout << "by factory:\n"; const auto var = std::variant<callable>{[] ()-> callable {return {}; }()}; std::cout << "finished\n"; }
尝试实现及编译错误
基于HTNW的思路实现variant_from函数,试图通过工厂和可变参数就地构造std::variant,但编译报错:
尝试代码
#include <variant> #include <tuple> struct to_construct { to_construct() = default; to_construct(to_construct&&) = delete; to_construct(to_construct const&) = delete; }; #include <iostream> struct callable { callable(int i) { std::cout << "callable():" << i << std::endl; } ~callable() { std::cout << "~callable()" << std::endl; } }; template<typename T> struct box { T x; template<typename F> box(F f) : x(f()) {} }; template<typename F> struct initializer { F init; operator auto() { return init(); } }; template<typename F> initializer(F) -> initializer<F>; template <typename ... Types, typename Factory, typename ... Args> std::variant<box<Types>...> variant_from(Factory &&f, Args &&... args) { return { initializer{ [tupleArgs = std::forward_as_tuple(args...), f = std::forward<Factory>(f)](){ return std::apply(f, tupleArgs); } } }; } int main() { { const auto var = std::variant<to_construct>(initializer{ []() -> to_construct { std::cout << "Success\n"; return {}; } }); } { const auto var = variant_from<callable>([](int i) { return callable(i);}, 42); } }
编译错误
<source>: In instantiation of 'box<T>::box(F) [with F = initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >; T = callable]': /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/variant:283:4: required from 'constexpr std::__detail::__variant::_Uninitialized<_Type, false>::_Uninitialized(std::in_place_index_t<0>, _Args&& ...) [with _Args = {initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >}; _Type = box<callable>]' /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/variant:385:4: required from 'constexpr std::__detail::__variant::_Variadic_union<_First, _Rest ...>::_Variadic_union(std::in_place_index_t<0>, _Args&& ...) [with _Args = {initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >}; _First = box<callable>; _Rest = {}]' /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/variant:460:4: required from 'constexpr std::__detail::__variant::_Variant_storage<false, _Types ...>::_Variant_storage(std::in_place_index_t<_Np>, _Args&& ...) [with long unsigned int _Np = 0; _Args = {initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >}; _Types = {box<callable>}]' /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/variant:557:20: required from 'constexpr std::__detail::__variant::_Variant_base<_Types>::_Variant_base(std::in_place_index_t<_Np>, _Args&& ...) [with long unsigned int _Np = 0; _Args = {initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >}; _Types = {box<callable>}]' /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/variant:1448:57: required from 'constexpr std::variant<_Types>::variant(std::in_place_index_t<_Np>, _Args&& ...) [with long unsigned int _Np = 0; _Args = {initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >}; _Tp = box<callable>; <template-parameter-2-4> = void; _Types = {box<callable>}]' /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/variant:1419:27: required from 'constexpr std::variant<_Types>::variant(_Tp&&) [with _Tp = initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >; <template-parameter-2-2> = void; <template-parameter-2-3> = void; _Tj = box<callable>; <template-parameter-2-5> = void; _Types = {box<callable>}]' <source>:61:5: required from 'std::variant<box<Types>...> variant_from(Factory&&, Args&& ...) [with Types = {callable}; Factory = main()::<lambda(int)>; Args = {int}]' <source>:78:46: required from here <source>:36:8: error: no match for call to '(initializer<variant_from<callable, main()::<lambda(int)>, int>(main()::<lambda(int)>&&, int&&)::<lambda()> >) ()' 36 | : x(f()) | ~^~
解决方案
错误原因
编译错误的核心是box<T>::box(F)构造函数中调用f()时,传入的f是initializer类型对象,但initializer仅定义了隐式转换运算符operator auto(),未定义可调用的operator(),导致编译器无法解析f()的调用。
修正代码
方式1:为initializer添加operator()
让initializer成为可调用对象,适配box构造函数中的f()调用:
#include <variant> #include <tuple> #include <iostream> struct to_construct { to_construct() = default; to_construct(to_construct&&) = delete; to_construct(to_construct const&) = delete; }; struct callable { callable(int i) { std::cout << "callable():" << i << std::endl; } ~callable() { std::cout << "~callable()" << std::endl; } }; template<typename T> struct box { T x; template<typename F> box(F f) : x(f()) {} }; template<typename F> struct initializer { F init; // 添加operator(),让initializer可以被调用 auto operator()() { return init(); } }; template<typename F> initializer(F) -> initializer<F>; template <typename ... Types, typename Factory, typename ... Args> std::variant<box<Types>...> variant_from(Factory &&f, Args &&... args) { return { initializer{ [tupleArgs = std::forward_as_tuple(args...), f = std::forward<Factory>(f)]() mutable { return std::apply(std::forward<Factory>(f), tupleArgs); } } }; } int main() { { const auto var = std::variant<to_construct>(initializer{ []() -> to_construct { std::cout << "Success\n"; return {}; } }); } { const auto var = variant_from<callable>([](int i) { return callable(i);}, 42); } }
方式2:修改box构造函数,利用隐式转换
保留initializer的隐式转换运算符,修改box的构造逻辑,直接通过隐式转换获取目标对象:
template<typename T> struct box { T x; template<typename F> box(F f) : x(f) // 触发initializer的隐式转换为T类型 {} }; // 其余代码不变,initializer仍保留operator auto()
优化:直接返回std::variant<Types...>
如果不需要box包装,希望直接返回目标类型的std::variant,可以简化实现,直接结合std::in_place_type和工厂函数的参数展开,确保对象在variant存储中就地构造:
#include <variant> #include <tuple> #include <iostream> struct callable { callable(int i) { std::cout << "callable():" << i << std::endl; } ~callable() { std::cout << "~callable()" << std::endl; } }; template <typename Target, typename Factory, typename ... Args> std::variant<Target> variant_from(Factory &&f, Args &&... args) { // 利用std::apply展开参数,直接在variant的存储中构造Target return std::apply( [&f](auto&&... unpacked_args) { return std::variant<Target>( std::in_place_type<Target>, std::forward<Factory>(f)(std::forward<decltype(unpacked_args)>(unpacked_args)...) ); }, std::forward_as_tuple(std::forward<Args>(args)...) ); } int main() { { const auto var = variant_from<callable>([](int i) { return callable(i);}, 42); } }
内容的提问来源于stack exchange,提问作者Sergey Kolesnik
相关产品推荐
相关产品推荐

