You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过工厂对象与可变参数就地初始化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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 12:20:27