std::reference_wrapper 打开包装纸
std::reference_wrapper unwrap the wrapper
简介。
在 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
接收一个容器,returns 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
returnsstd::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;
}
它产生了支撑 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)>
),类型(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!。
我认为问题在于,*b
return 是一个常量值(因为容器是通过常量引用传递的)。您的 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 &>&>>);
}
这应该 return 任何非 reference_wrapper
的原始类型和 cv 限定的 reference_wrapper
及其引用的内部类型。
说明:下面我会调用OPUNWRAP
的原文unwrap
来区别我的版本。我们想要在 std::decay_t<T>
是 std::reference_wrapper
时调用 UNWRAP
的 reference_wrapper 规范。现在,如果我们总是用 std::decay_t<T>
而不是 T
调用 UNWRAP
,这就可以简单地完成。
问题是,如果 T
不是 reference_wrapper,这将删除所有限定条件,即 UNWRAP<std::decay_t<const int>>
是 int
,而我们需要它成为 const int
。
为了解决这个问题,我们定义了我们 template<class type_t, class orig_t> struct unwrap_impl
。我们希望始终将衰减类型传递给第一个参数,将原始类型(衰减前)作为第二个参数传递。然后,我们可以将一般情况 orig_t
作为结果类型(如 using type = orig_t
所做的那样)。
对于规范,我们定义template<class type_t, class V> struct unwrap_impl<std::reference_wrapper<type_t>, V>
。只要 type_t
是 reference_wrapper,即当原始类型是 reference_wrapper 的某种限定时,这将适用。我们不关心第二个参数(它将是原始类型),所以我们忽略它。然后我们将 reference_wrapper 的内部类型作为类型 (using type = type_t;
).
然后我们通过基本定义 template<class type_t> unwrap = detail::unwrap_impl<std::decay_t<type_t>, type_t>;
来调用 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
简介。
在 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
接收一个容器,returns 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
returnsstd::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;
}
它产生了支撑 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)>
),类型(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!。
我认为问题在于,*b
return 是一个常量值(因为容器是通过常量引用传递的)。您的 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 &>&>>);
}
这应该 return 任何非 reference_wrapper
的原始类型和 cv 限定的 reference_wrapper
及其引用的内部类型。
说明:下面我会调用OPUNWRAP
的原文unwrap
来区别我的版本。我们想要在 std::decay_t<T>
是 std::reference_wrapper
时调用 UNWRAP
的 reference_wrapper 规范。现在,如果我们总是用 std::decay_t<T>
而不是 T
调用 UNWRAP
,这就可以简单地完成。
问题是,如果 T
不是 reference_wrapper,这将删除所有限定条件,即 UNWRAP<std::decay_t<const int>>
是 int
,而我们需要它成为 const int
。
为了解决这个问题,我们定义了我们 template<class type_t, class orig_t> struct unwrap_impl
。我们希望始终将衰减类型传递给第一个参数,将原始类型(衰减前)作为第二个参数传递。然后,我们可以将一般情况 orig_t
作为结果类型(如 using type = orig_t
所做的那样)。
对于规范,我们定义template<class type_t, class V> struct unwrap_impl<std::reference_wrapper<type_t>, V>
。只要 type_t
是 reference_wrapper,即当原始类型是 reference_wrapper 的某种限定时,这将适用。我们不关心第二个参数(它将是原始类型),所以我们忽略它。然后我们将 reference_wrapper 的内部类型作为类型 (using type = type_t;
).
然后我们通过基本定义 template<class type_t> unwrap = detail::unwrap_impl<std::decay_t<type_t>, type_t>;
来调用 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