Введение.
В 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;
}
Получает проппер искаженные имена * 1 054 *:
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
.
Код доступен на Попробуйте онлайн! .