处理包含仅移动类型的变体

问题描述

考虑以下代码:

#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,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
    );
}

我过去曾通过使用自定义变体类型转换 similar to the one implemented here 来转换为仅包含需要处理相同类型的类型的变体,然后使用带有自动类型的 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);
}

我们添加了一个 trait 来调度,然后使用 if constexpr 来拥有 2 个函数体。

,我们有更多选择。

[]<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)

中的 之前,我们可以使用调度助手:

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);
}

回到(你从哪里得到你的变体?),你可以创建一个外部类:

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 );

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...