【问题标题】:std::reference_wrapper unwrap the wrapperstd::reference_wrapper 解开包装器
【发布时间】:2020-01-30 10:57:36
【问题描述】:

简介。

在 C++ 中,我们不能创建引用容器:

std::vector<int&> vri;
In instantiation of ‘class __gnu_cxx::new_allocator<int&>’:
required from ‘class std::allocator<int&>’
required from ‘struct std::_Vector_base<int&, std::allocator<int&> >’
required from ‘class std::vector<int&>’
required from here
error: forming pointer to reference type ‘int&’
       typedef _Tp*       pointer;
                          ^~~~~~~

内部实现需要创建一个指向所包含类型的指针,从而导致指向引用的指针禁止类型。

幸运的是,std::reference_wrapper 存在:

int x{1}, y{2}, z{3};
std::vector<std::reference_wrapper<int>> vr{x, y, z};
for (auto &v : vr)
    ++v;
std::cout << x << ' ' << y << ' ' << z << '\n';

上面的代码显示2 3 4

问题。

我正在开发一个 C++ 过滤器实用程序,例如过滤器where 接收一个容器并将std::reference_wrapper 返回到满足条件的包含对象:

template <typename container_t> auto range(const container_t &container)
{ return std::tuple{std::begin(container), std::end(container)}; };

template <typename container_t, typename predicate_t>
auto where(const container_t &container, predicate_t predicate)
{
    auto [b, e] = range(container);
    using type = std::remove_reference_t<decltype(*b)>;
    using reference = std::reference_wrapper<type>;

    std::vector<reference> result{};

    std::copy_if(b, e, std::back_inserter(result), predicate);

    return result;
}

下面的代码显示2 3 6 7

int main()
{
    std::vector v{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

    for (auto &x : where(v, [](auto n){ return n & 0b10; }))
        std::cout << x << ' ';

    return 0;
}

但我在链接过滤器时遇到问题:

for (const auto &x :
    where(where(v, [](auto n){ return n & 0b10; }), [](auto n){ return n & 0b1; })) {
    std::cout << x << ' ';
}
no match for ‘operator<<’ (operand types are ‘std::ostream’ {aka ‘std::basic_ostream<char>’} and ‘const std::reference_wrapper<const std::reference_wrapper<const int> >’)
   std::cout << x << ' ';
   ~~~~~~~~~~^~~~

内部where 返回std::vector&lt;std::refernce_wrapper&lt;int&gt;&gt;,所以外部将使用std::vector&lt;std::refernce_wrapper&lt;const std::refernce_wrapper&lt;const int&gt;&gt;&gt;

我尝试了什么?

为了解决这个问题,我尝试创建一个解包std::reference_wrapper&lt;T&gt;的模板:

template <typename type_t>
struct unwrap
{
    using type = type_t;
};

template <typename type_t>
struct unwrap<std::reference_wrapper<type_t>>
{
    using type = type_t;
};

template <typename type_t>
using unwrap_t = typename unwrap<type_t>::type;

到目前为止,它看起来正在工作:

int main()
{
    using ri = std::reference_wrapper<int>;
    using rf = std::reference_wrapper<float>;
    using rri = std::reference_wrapper<ri>;
    using rrri = std::reference_wrapper<rri>;

    std::cout
        << typeid(int).name() << '\t' << typeid(unwrap_t<int>).name() << '\n'
        << typeid(float).name() << '\t' << typeid(unwrap_t<float>).name() << '\n'
        << typeid(ri).name() << '\t' << typeid(unwrap_t<ri>).name() << '\n'
        << typeid(rf).name() << '\t' << typeid(unwrap_t<rf>).name() << '\n'
        << typeid(rri).name() << '\t' << typeid(unwrap_t<rri>).name() << '\n'
        << typeid(rrri).name() << '\t' << typeid(unwrap_t<rrri>).name();

    return 0;
}

它产生了propper mangled names:

  i   i
  f   f
  St17reference_wrapperIiE    i
  St17reference_wrapperIfE    f
  St17reference_wrapperIS_IiEE    St17reference_wrapperIiE
  St17reference_wrapperIS_IS_IiEEE    St17reference_wrapperIS_IiEE

整数和浮点(intfloat)保持不变,整数和浮点包装器被展开,嵌套的包装器被展开一层。

但它在where内部不起作用:

template <typename container_t, typename predicate_t>
auto where(const container_t &container, predicate_t predicate)
{
    auto [b, e] = range(container);
    using type = unwrap_t<std::remove_reference_t<decltype(*b)>>;
    //           ^^^^^^^^ <--- Unwraps iterator's inner type
    using reference = std::reference_wrapper<type>;

    std::vector<reference> result{};

    std::copy_if(b, e, std::back_inserter(result), predicate);

    // Debug
    std::cout
        << __PRETTY_FUNCTION__ << "\n"
        << '\t' << "decltype(*b) = " << typeid(decltype(*b)).name() << '\n'
        << '\t' << "unwrap *b = " << typeid(unwrap_t<decltype(*b)>).name() << '\n'
        << '\t' << "type = " << typeid(type).name() << '\n'
        << '\t' << "reference = " << typeid(reference).name() << '\n'
        << '\t' << "unwrap type = " << typeid(unwrap_t<type>).name() << '\n';

    return result;
}

int main()
{
    std::vector v{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

    for (const auto &x :
        where(where(v, [](auto n){ return n & 0b10; }), [](auto n){ return n & 0b1; })) {
        std::cout << &x << ' ';
    }

    return 0;
}

调试日志到where 显示解包器在第一次调用时工作(它对类型没有任何作用),但在第二次调用时没有:

  auto where(const container_t&, predicate_t) [with container_t = std::vector<int, std::allocator<int> >; predicate_t = main()::<lambda(auto:1)>]
      decltype(*b) = i
      unwrap *b = i
      type = i
      reference = St17reference_wrapperIKiE
      unwrap type = i
  auto where(const container_t&, predicate_t) [with container_t = std::vector<std::reference_wrapper<const int>, std::allocator<std::reference_wrapper<const int> > >; predicate_t = main()::<lambda(auto:2)>]
      decltype(*b) = St17reference_wrapperIKiE
      unwrap *b = St17reference_wrapperIKiE
      type = St17reference_wrapperIKiE
      reference = St17reference_wrapperIKS_IKiEE
      unwrap type = St17reference_wrapperIKiE

在内部调用中,输入容器是std::vector&lt;int&gt;,因此迭代器内部类型(decltype(*b))、展开(unwrap_t&lt;decltype(*b)&gt;)、tye 类型(type)和展开类型(unwrap_t&lt;type&gt;)是int,只有referencestd::reference_wrapper

在外部调用中,输入容器是std::vector&lt;std::reference_wrapper&lt;const int&gt;&gt;,所有类型(reference 除外)都是std::reference_wrapper&lt;const int&gt;,就好像解包器忽略了输入类型一样。

问题。

我的解包器做错了什么?我认为这个问题可能与const 传播有关。

Try it online! 上提供的代码。

【问题讨论】:

  • 您能否将问题中的所有代码束 sn-ps 重新组织成一个任何人都可以轻松剪切/粘贴、尝试编译和重现您的问题的代码示例。
  • 在拼凑各种代码 sn-ps 后,显示的代码通过 gcc 9.2 编译没有问题。这是编译器中的错误,或者显示的代码不是导致编译错误的真实代码。在看到minimal reproducible example 之前,我不会再浪费时间了。
  • @SamVarshavchik 如果您已经阅读到最后,您可以复制在线链接中的代码。顺便说一句,没有编译错误。您只需要花 5 秒时间阅读代码 sn-p。
  • @SamVarshavchik 您在问题底部的最后一个链接中有一个工作代码 sn-p。

标签: c++ templates wrapper


【解决方案1】:

我认为问题在于,*b 返回一个 const 值(因为容器是通过 const 引用传递的)。您的 unwrap 仅适用于非常量、非易失性 reference_wrapper。在这个问题上我会如下:

#include <functional>

namespace detail{
template <typename type_t, class  orig_t>
struct unwrap_impl
{
    using type = orig_t;
};

template <typename type_t, class V>
struct unwrap_impl<std::reference_wrapper<type_t>,V>
{
    using type = type_t;
};
}

template<class T>
struct unwrap {
  using type = typename detail::unwrap_impl<std::decay_t<T>, T>::type;
};

template <typename type_t>
using unwrap_t = typename unwrap<type_t>::type;

int main() {
    static_assert(std::is_same_v<const int&, unwrap_t<const int &>>);
        static_assert(std::is_same_v<const int&, unwrap_t<std::reference_wrapper<const int &>>>);
        static_assert(std::is_same_v<const int&, unwrap_t<const std::reference_wrapper<const int &>&>>);
}

这应该返回非 reference_wrapper 的任何东西的原始类型和 cv 限定的 reference_wrappers 的内部类型及其引用。


说明:我会在下面调用原来的unwrap from OP UNWRAP 来区分我的版本。只要std::decay_t&lt;T&gt;std::reference_wrapper,我们就想调用UNWRAP 的reference_wrapper 规范。现在,如果我们总是用std::decay_t&lt;T&gt; 而不是T 调用UNWRAP,这可以很简单地完成。

问题在于,如果T 不是reference_wrapper,这将删除所有限定条件,即UNWRAP&lt;std::decay_t&lt;const int&gt;&gt;int,而我们希望它是const int

为了解决这个问题,我们定义了我们template&lt;class type_t, class orig_t&gt; struct unwrap_impl。我们希望始终将第一个参数的衰减类型和原始类型(衰减之前)作为第二个参数传递。然后,我们可以将一般情况 orig_t 作为结果类型(由 using type = orig_t 完成)。

对于规范,我们定义template&lt;class type_t, class V&gt; struct unwrap_impl&lt;std::reference_wrapper&lt;type_t&gt;, V&gt;。这将适用于 type_t 是 reference_wrapper 时,即当原始类型是 reference_wrapper 的某种限定时。我们不关心第二个参数(它将是原始类型),所以我们忽略它。然后我们将reference_wrapper的内部类型作为类型(using type = type_t;)。

然后我们通过基本定义template&lt;class type_t&gt; unwrap = detail::unwrap_impl&lt;std::decay_t&lt;type_t&gt;, type_t&gt;; 来调用unwrap_impl(这是伪代码,但我认为这样更清楚。

一些例子:

unwrap<int> -> unwrap_impl<int, int> -> int
unwrap<const int> -> unwrap_impl<int, const int> -> const int
unwrap<std::reference_wrapper<const int>> -> unwrap_impl<std::reference_wrapper<const int>, std::reference_wrapper<const int>> -> const int
unwrap<const std::reference_wrapper<const int>> -> unwrap_impl<const std::reference_wrapper<const int>, const std::reference_wrapper<const int>> -> const int

(又是更多伪代码,但我希望它清楚)

编辑:修复了一些错误。

Edit2:似乎工作:link

【讨论】:

  • 你能解释一下unwrap_impl吗?我发现很难跟上。 :(
  • 没问题!我添加了一个解释,我希望它清楚。如果您想了解更多信息,请告诉我。
  • 模板参数的默认值orig_t = type_t不是必须的吧?
  • 是的,我最初认为这将使我们能够摆脱规范中的第二个模板参数,但没有成功。我会删除它。
猜你喜欢
  • 2018-04-08
  • 1970-01-01
  • 1970-01-01
  • 2020-02-09
  • 2015-07-27
  • 2021-03-20
  • 1970-01-01
  • 1970-01-01
  • 2020-12-23
相关资源
最近更新 更多