处理包含仅移动类型的变体
dealing with variants containing move-only types
考虑以下代码:
#include <iostream>
#include <variant>
#include <memory>
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...)->overloaded<Ts...>;
struct foo {
int f;
foo(int n) : f(n) {}
};
struct bar {
std::string b;
};
using unflattened_variant = std::variant<int, std::string, std::unique_ptr<foo>, std::unique_ptr<bar>>;
using flattened_variant = std::variant<int, std::string, foo, bar>;
flattened_variant flatten(const unflattened_variant& v) {
return std::visit(
overloaded{
[](int v) -> flattened_variant {
return v;
},
[](const std::string& s) -> flattened_variant {
return s;
},
[](const std::unique_ptr<foo>& f) -> flattened_variant {
return *f;
},
[](const std::unique_ptr<bar>& b) -> flattened_variant {
return *b;
},
},
v
);
}
int main()
{
unflattened_variant uv{ std::make_unique<foo>(42) };
auto fv = flatten(uv);
std::cout << std::get<foo>(fv).f << "\n";
}
这是一个玩具示例,它说明了我在真实代码中 运行 遇到的情况。我想简化 flatten(...)
的实现,这样当变体中有更多类型时它就不那么冗长了。
基本上情况是,我有一个包含一些简单类型和一些我想使用的仅移动类型的变体。我需要执行的操作对于所有简单类型和所有仅移动类型都是相同的;但是,我想不出只使用两个访问函数来处理这两种情况(简单或仅移动)的方法。例如这是非法的 C++,但说明了我想做什么
flattened_variant flatten(const unflattened_variant& v) {
return std::visit(
overloaded{
[](const std::unique_ptr<auto>& u_ptr) -> flattened_variant {
return *u_ptr;
},
[](auto simple_value) -> flattened_variant {
return simple_value;
},
},
v
);
}
我过去处理过这样的情况,方法是使用自定义变体转换 ,转换为仅包含需要相同处理的类型的变体,然后使用 lambda 获取作为访问者的自动参数;但是,这种类型转换在这种情况下不起作用,因为您不能复制 unique_ptrs 并且不能创建包含引用的变体。我想我可以编写一个函数来转换为指针的变体,但我想知道是否有更简单的方法。
template<template<class...>class, class> struct is_instance_of:std::false_type{};
template<template<class...>class Z, class...Ts> struct is_instance_of<Z,Z<Ts...>>:std::true_type{};
template<template<class...>class Z, class T>
constexpr bool is_instance_of_v=is_instance_of<Z,T>::value;
flattened_variant flatten(unflattened_variant const& v) {
return std::visit([](auto const& e)->flattened_variant{
using T = std::decay_t<decltype(e)>;
if constexpr (is_instance_of_v<std::unique_ptr, T>){
return *e;
else
return e;
}, v);
}
我们添加一个特征来调度,然后使用 if constexpr 有 2 个函数体。
在 c++20 中,我们有更多选择。
[]<class T>(T const& e)->flattened_variant{
if constexpr (is_instance_of_v<std::unique_ptr, T>){
然后,回到重载解决方案,我们有:
[]<class T>(std::unique_ptr<T> const&)
或
template<class T, template<class...>class Z>
concept instance_of=is_instance_of<Z,T>::value;
然后
[](instance_of<std::unique_ptr> auto const& e)
或
[]<<instance_of<std::unique_ptr> T>(T const& e)
在 c++17 in c++14 之前,我们可以使用调度助手:
template<class T0, class T1>
constexpr T0&& constexpr_branch( std::true_type, T0&& t0, T1&& ) { return std::forward<T0>(t0); }
template<class T0, class T1>
constexpr T1&& constexpr_branch( std::false_type, T0&&, T1&& t1 ) { return std::forward<T1>(t1); }
flattened_variant flatten(unflattened_variant const& v) {
return std::visit([](auto const& e)->flattened_variant{
using T = std::decay_t<decltype(e)>;
return constexpr_branch(
is_instance_of<std::unique_ptr, T>,
[](auto const& e){return *e;},
[](auto const& e){return e;}
)(e);
}, v);
}
回到 c++11(你从哪里得到你的变体?),你可以制作一个外部 class:
template<class R>
struct flatten {
template<class T>
R operator()(std::unique_ptr<T> const& p)const{
return *p;
}
template<class T>
R operator()(T const& v)const{
return v;
}
};
然后做一个
return std::visit( flatten<flattened_variant>{}, v );
考虑以下代码:
#include <iostream>
#include <variant>
#include <memory>
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...)->overloaded<Ts...>;
struct foo {
int f;
foo(int n) : f(n) {}
};
struct bar {
std::string b;
};
using unflattened_variant = std::variant<int, std::string, std::unique_ptr<foo>, std::unique_ptr<bar>>;
using flattened_variant = std::variant<int, std::string, foo, bar>;
flattened_variant flatten(const unflattened_variant& v) {
return std::visit(
overloaded{
[](int v) -> flattened_variant {
return v;
},
[](const std::string& s) -> flattened_variant {
return s;
},
[](const std::unique_ptr<foo>& f) -> flattened_variant {
return *f;
},
[](const std::unique_ptr<bar>& b) -> flattened_variant {
return *b;
},
},
v
);
}
int main()
{
unflattened_variant uv{ std::make_unique<foo>(42) };
auto fv = flatten(uv);
std::cout << std::get<foo>(fv).f << "\n";
}
这是一个玩具示例,它说明了我在真实代码中 运行 遇到的情况。我想简化 flatten(...)
的实现,这样当变体中有更多类型时它就不那么冗长了。
基本上情况是,我有一个包含一些简单类型和一些我想使用的仅移动类型的变体。我需要执行的操作对于所有简单类型和所有仅移动类型都是相同的;但是,我想不出只使用两个访问函数来处理这两种情况(简单或仅移动)的方法。例如这是非法的 C++,但说明了我想做什么
flattened_variant flatten(const unflattened_variant& v) {
return std::visit(
overloaded{
[](const std::unique_ptr<auto>& u_ptr) -> flattened_variant {
return *u_ptr;
},
[](auto simple_value) -> flattened_variant {
return simple_value;
},
},
v
);
}
我过去处理过这样的情况,方法是使用自定义变体转换
template<template<class...>class, class> struct is_instance_of:std::false_type{};
template<template<class...>class Z, class...Ts> struct is_instance_of<Z,Z<Ts...>>:std::true_type{};
template<template<class...>class Z, class T>
constexpr bool is_instance_of_v=is_instance_of<Z,T>::value;
flattened_variant flatten(unflattened_variant const& v) {
return std::visit([](auto const& e)->flattened_variant{
using T = std::decay_t<decltype(e)>;
if constexpr (is_instance_of_v<std::unique_ptr, T>){
return *e;
else
return e;
}, v);
}
我们添加一个特征来调度,然后使用 if constexpr 有 2 个函数体。
在 c++20 中,我们有更多选择。
[]<class T>(T const& e)->flattened_variant{
if constexpr (is_instance_of_v<std::unique_ptr, T>){
然后,回到重载解决方案,我们有:
[]<class T>(std::unique_ptr<T> const&)
或
template<class T, template<class...>class Z>
concept instance_of=is_instance_of<Z,T>::value;
然后
[](instance_of<std::unique_ptr> auto const& e)
或
[]<<instance_of<std::unique_ptr> T>(T const& e)
在 c++17 in c++14 之前,我们可以使用调度助手:
template<class T0, class T1>
constexpr T0&& constexpr_branch( std::true_type, T0&& t0, T1&& ) { return std::forward<T0>(t0); }
template<class T0, class T1>
constexpr T1&& constexpr_branch( std::false_type, T0&&, T1&& t1 ) { return std::forward<T1>(t1); }
flattened_variant flatten(unflattened_variant const& v) {
return std::visit([](auto const& e)->flattened_variant{
using T = std::decay_t<decltype(e)>;
return constexpr_branch(
is_instance_of<std::unique_ptr, T>,
[](auto const& e){return *e;},
[](auto const& e){return e;}
)(e);
}, v);
}
回到 c++11(你从哪里得到你的变体?),你可以制作一个外部 class:
template<class R>
struct flatten {
template<class T>
R operator()(std::unique_ptr<T> const& p)const{
return *p;
}
template<class T>
R operator()(T const& v)const{
return v;
}
};
然后做一个
return std::visit( flatten<flattened_variant>{}, v );