【发布时间】: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<std::refernce_wrapper<int>>,所以外部将使用std::vector<std::refernce_wrapper<const std::refernce_wrapper<const int>>>。
我尝试了什么?
为了解决这个问题,我尝试创建一个解包std::reference_wrapper<T>的模板:
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
整数和浮点(int、float)保持不变,整数和浮点包装器被展开,嵌套的包装器被展开一层。
但它在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<int>,因此迭代器内部类型(decltype(*b))、展开(unwrap_t<decltype(*b)>)、tye 类型(type)和展开类型(unwrap_t<type>)是int,只有reference 是std::reference_wrapper。
在外部调用中,输入容器是std::vector<std::reference_wrapper<const int>>,所有类型(reference 除外)都是std::reference_wrapper<const int>,就好像解包器忽略了输入类型一样。
问题。
我的解包器做错了什么?我认为这个问题可能与const 传播有关。
Try it online! 上提供的代码。
【问题讨论】:
-
您能否将问题中的所有代码束 sn-ps 重新组织成一个任何人都可以轻松剪切/粘贴、尝试编译和重现您的问题的代码示例。
-
在拼凑各种代码 sn-ps 后,显示的代码通过 gcc 9.2 编译没有问题。这是编译器中的错误,或者显示的代码不是导致编译错误的真实代码。在看到minimal reproducible example 之前,我不会再浪费时间了。
-
@SamVarshavchik 如果您已经阅读到最后,您可以复制在线链接中的代码。顺便说一句,没有编译错误。您只需要花 5 秒时间阅读代码 sn-p。
-
@SamVarshavchik 您在问题底部的最后一个链接中有一个工作代码 sn-p。