【问题标题】:Is it possible to in-place initailize std::variant given a factory object and variadic arguments list?给定工厂对象和可变参数列表,是否可以就地初始化 std::variant ?
【发布时间】:2022-09-18 01:14:47
【问题描述】:

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::cout << \"by factory:\\n\";
        const auto var = std::variant<callable>{[] ()-> callable {return {}; }()};
        std::cout << \"finished\\n\";
    }

https://godbolt.org/z/rY5WG1hGn

#include <variant>

#include <iostream>

int main(int, char **)
{
    struct callable
    {
        callable()
        {
            std::cout << \"callable()\" << std::endl;
        }

        ~callable()
        {
            std::cout << \"~callable()\" << std::endl;
        }
    };

    {
        std::cout << \"in_place_type:\\n\";
        const auto var = std::variant<callable>(std::in_place_type<callable>);
        std::cout << \"finished\\n\";
    }
    std::cout << \'\\n\';
        
    {
        std::cout << \"by factory:\\n\";
        const auto var = std::variant<callable>{[] ()-> callable {return {}; }()};
        std::cout << \"finished\\n\";
    }
}

我认为在这种情况下它不起作用,因为 std::variant(T &amp;&amp;t) 构造函数与工厂一起使用,并且不能使用复制省略。


我试图详细说明HTNW\ 的答案(从参数创建一个对象),但得到一个编译器错误:

#include <variant>
#include <tuple>

struct to_construct {
  // must NOT exist
  // template<typename F>
  // to_construct(initializer<F>) { std::cout << \"Foiled!\\n\"; }
  // or (more likely)
  // template<typename T>
  // to_construct(T) { std::cout << \"Foiled again!\\n\"; }
  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())
      |       ~^~
ASM generation compiler returned: 1
<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())
      |       ~^~
Execution build compiler returned: 1

https://godbolt.org/z/8MMMEe3jx

  • T&amp;&amp; 构造函数必须绑定到一个对象,所以没有复制省略。
  • @NathanOliver 我猜。 std::variant 没有构造函数重载,它将采用工厂对象。我只是不确定是否有任何解决方法。所以说“不可能”的答案是合法的
  • 如果您使用的是box,则不应使用initializer,并在答案中进行了解释。无需使用variant_from 中的初始化捕获。只需[&amp;] 就可以了。

标签: c++ templates c++17 copy-elision


【解决方案1】:

是的。

工厂需要包装在一个类型中,并转换为包含类型,并且包含类型需要不是有一个优先的构造函数。

template<typename F>
struct initializer {
  F init;
  operator auto() {
    return init();
  }
};
template<typename F>
initializer(F) -> initializer<F>;

struct to_construct {
  // must NOT exist
  // template<typename F>
  // to_construct(initializer<F>) { std::cout << "Foiled!\n"; }
  // or (more likely)
  // template<typename T>
  // to_construct(T) { std::cout << "Foiled again!\n"; }
  to_construct() = default;
  to_construct(to_construct&&) = delete;
  to_construct(to_construct const&) = delete;
};

int main() {
  std::variant<to_construct, int> v;
  v.emplace<to_construct>(initializer{[]() -> to_construct { std::cout << "Success\n"; return {}; }});
}

包含的类型不能有与initializer 匹配的构造函数这一点很重要:这意味着您不能安全地对您无法控制的类型的variants 执行此技巧。如果您需要以这种方式在variant 中构造一些外部T,请将T 包装在您自己控件的box 中,在这种情况下,您可以直接从工厂添加构造函数。

// do NOT do this, because it might fail silently and weirdly
// template<typename T>
// void foo() {
//   std::variant<int, T> v;
//   v.emplace<1>(initializer{[]() -> T { return {}; }});
// }

template<typename T>
struct box {
  T x;
  template<typename F>
  box(F f) : x(f()) { }
};
template<typename T>
void foo() {
  std::variant<int, box<T>> v;
  v.template emplace<1>([]() -> T { return {}; });
}

IE。您可以在工厂的 variant 中构造一个对象,而无需更改 variant 类型有时(当您控制替代品时),但通常您可能需要将一些替代品更改为boxs。


基于上述内容的扩展代码的问题是您尝试将initializerbox 一起使用。 box 不需要initializer。它直接采用 lambdas。此外,由于初始化 lambda 不必超过 variant 的构造,它几乎不应该按值捕获。此外,如果您不想每次都手动指定您想要的替代方案,您应该让box 的构造函数在它不起作用时退出重载决议。

// it is convention in the C++ standard libraries to not bother forwarding functors
template<typename... Types, typename Factory, typename... Args>
std::variant<box<Types>...> variant_from(Factory f, Args&&... args) {
    return [&]() { return f(std::forward<Args>(args)...); };
}

template<typename T>
struct box {
  T x;
  template<
    typename F,
    typename = std::enable_if_t< // unsure if this is quite the right condition, it's a bit stricter than just "x(f()) would compile"
      std::is_same_v<
        T,
        std::decay_t<decltype(std::declval<F&>()())>>>>
  box(F f) : x(f()) { }
};

【讨论】:

  • 那么如果我有控制变体的类型是否有移动构造函数?将它们包装在变体中的不可移动和不可复制的对象中是否有帮助?那么从参数构造的工厂呢?标记initializer&lt;T&gt; 构造函数deleted 就足够了吗?
  • 如果其他人正在设置 variant 类型您不能确保您正在构建的替代方案缺少与initializer 匹配的构造函数,您不走运。我的代码中没有任何内容需要移动构造函数。我不知道你在建议什么包装;您唯一需要的是box&lt;T&gt;delete'd initializer&lt;T&gt; 构造函数也会破坏事物。它需要不被宣布.在这里有一个带参数的工厂是没有意义的。你的意思是一个工厂,例如捕获变量并返回由这些组成的对象?
  • godbolt.org/z/4d6Px41r4 第二个没有编译。此外,就我而言,用户提供了Factory(args...),我通过为工厂提供实际参数来初始化变体
  • @SergeyKolesnik box 旨在直接从 lambda 构造。它不需要initializer。删除 initializer 并编译。再一次,初始化器有参数是没有意义的。你的意思是你正在做类似template&lt;typename F&gt; void foo(F f) { int i = 5; std::variant&lt;something&gt;(initializer{[&amp;]() -&gt; something { return f(i, "a", false); }});的事情。初始化器没有参数(在 lambda 中为空 ()),不能有参数,也不需要参数。
  • C++ 标准库中的约定是不打扰转发函子- 如果一个工厂是有状态的,并且无法复制怎么办?并且归调用者所有?
猜你喜欢
  • 2010-09-24
  • 1970-01-01
  • 1970-01-01
  • 2022-08-14
  • 2010-10-15
  • 2021-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多