Why doesn't std::ranges::common_view compile in the code below?
#include <ranges>
#include <vector>
#include <algorithm>
template <class T>
struct IteratorSentinel {};
template <class T>
class Iterator
{
public:
using iterator_category = std::input_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = value_type*;
using reference = value_type&;
Iterator() = default;
Iterator(const Iterator&) = delete;
Iterator& operator = (const Iterator&) = delete;
Iterator(Iterator&& other) = default;
Iterator& operator = (Iterator&& other) = default;
T* operator-> () { return cur(); }
T& operator* () { return *cur(); }
T& operator* () const { return *cur(); };
bool operator== (const IteratorSentinel<T>&) const noexcept;
Iterator& operator++ ();
void operator++ (int);
private:
T* cur() const
{
return pCur;
}
T* pCur = nullptr;
};
static_assert(std::input_iterator<Iterator<int>>);
template <class T>
auto make_range()
{
return std::ranges::subrange(Iterator<T>(), IteratorSentinel<T>{});
}
int main()
{
auto r = make_range<int>();
auto cr = std::ranges::common_view{ r };
std::vector<int> v;
std::copy(cr.begin(), cr.end(), std::back_inserter(v));
return 0;
}
Can't figure out what template parameter does it require.
MSVC2022 error (/std:c++latest):
error C2641: cannot deduce template arguments for 'std::ranges::common_view'
error C2893: Failed to specialize function template 'std::ranges::common_view<_Vw> std::ranges::common_view(_Vw) noexcept(<expr>)'
GCC12 errors:
prog.cc: In function 'int main()':
prog.cc:67:43: error: class template argument deduction failed:
67 | auto cr = std::ranges::common_view{ r };
| ^
prog.cc:67:43: error: no matching function for call to 'common_view(std::ranges::subrange<Iterator<int>, IteratorSentinel<int>, std::ranges::subrange_kind::unsized>&)'
In file included from prog.cc:1:
/opt/wandbox/gcc-12.1.0/include/c++/12.1.0/ranges:3724:7: note: candidate: 'template<class _Vp> common_view(_Vp)-> std::ranges::common_view<_Vp>'
3724 | common_view(_Vp __r)
| ^~~~~~~~~~~
Your range is built out of an iterator/sentinel pair. The definition of a common range is a range where the sentinel type is an iterator. So the range itself is not a common range.
common_view can generate a common range from a non-common range. Which means that it will have to create two iterators. And since it starts the process with only one iterator, that means that, at some point, it must copy that iterator (thus creating two usable iterators).
Which it can't do because your iterator is non-copyable. Which is why common_view has an explicit requirement that the iterator is copyable.
Related
As practice I tried to make a wrapper around raw pointers to have cleaner memory management when using CUDA. Basically I made a PointerBase template that has a PointerBase specialization for const pointers. This template is the base template for a HostPointer and a DevicePointer template that manage memory on CPU side and GPU side respectively (but this is out of scope of this question). I struggle with the conversion from a PointerBase<T> object into the PointerBase<const T> specialization.
(Keep in mind this is more about practicing C++ templates than production use).
Here is a simple code demonstrating the behavior.
template <typename T>
struct Pointer
{
T* ptr_;
Pointer() = default;
Pointer<T>& operator=(const Pointer<T>& other) = default;
operator Pointer<const T>() const { return Pointer<const T>(ptr_); }
T* get() const { return ptr_; }
};
template <typename T>
struct Pointer<const T>
{
const T* ptr_;
Pointer() = default;
Pointer<const T>& operator=(const Pointer<const T>& other) = default;
const T* get() const { return ptr_; }
};
template <typename T>
T dereference(Pointer<const T> data)
{
return *data.get();
}
int main()
{
int value = 7.0;
Pointer<int> ptr{&value};
//int tmp3 = dereference(ptr); // not compiling
int tmp4 = dereference(Pointer<const int>(ptr)); // compiling
return 0;
}
This code fails to compile with the following error (gcc 8.4.0 on ubuntu 20.4, with c++14 standard)
main.cpp: In function ‘int main()’:
main.cpp:41:31: error: no matching function for call to ‘dereference(Pointer<int>&)’
int tmp3 = dereference(ptr); // not compiling
^
main.cpp:32:3: note: candidate: ‘template<class T> T dereference(Pointer<const T>)’
T dereference(Pointer<const T> data)
^~~~~~~~~~~
main.cpp:32:3: note: template argument deduction/substitution failed:
main.cpp:41:31: note: types ‘const T’ and ‘int’ have incompatible cv-qualifiers
int tmp3 = dereference(ptr); // not compiling
^
Why is the conversion operator not considered for calling the dereference function ?
I feel there is a connection to the fact that the Pointer<const T> type is a specialization of the Pointer template. But I still don't understand why a conversion is not happening.
I get the same result when defining a constructor in Pointer<const T> taking a Pointer<T> as a parameter.
Any help ?
PS : Interestingly, I get the same behavior with std::shared_ptr.
#include <memory>
template <typename T>
T dereference(std::shared_ptr<const T> data)
{
return *data.get();
}
int main()
{
std::shared_ptr<int> sptr(new int[10]);
int tmp1 = dereference(sptr); // not compiling
//int tmp2 = dereference(std::shared_ptr<const int>(sptr)); //compiling
return 0;
}
main.cpp: In function ‘int main()’:
main.cpp:9:32: error: no matching function for call to ‘dereference(std::shared_ptr<int>&)’
int tmp1 = dereference(sptr); // not compiling
^
main.cpp:4:3: note: candidate: ‘template<class T> T dereference(std::shared_ptr<const _Tp>)’
T dereference(std::shared_ptr<const T> data) { return *data.get(); }
^~~~~~~~~~~
main.cpp:4:3: note: template argument deduction/substitution failed:
main.cpp:9:32: note: types ‘const _Tp’ and ‘int’ have incompatible cv-qualifiers
int tmp1 = dereference(sptr); // not compiling
^
I'm working on an Advent of Code challenge (2021 day 18). Just as a test I tried compiling it on different compilers. While GCC (11.2) and MSVC (19.30) think it's fine, Clang (13.0.0) throws a list of errors. link to compiler explorer
/opt/compiler-explorer/gcc-11.2.0/lib/gcc/x86_64-linux-gnu/11.2.0/../../../../include/c++/11.2.0/bits/alloc_traits.h:514:4: error: no matching function for call to 'construct_at'
std::construct_at(__p, std::forward<_Args>(__args)...);
^~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-11.2.0/lib/gcc/x86_64-linux-gnu/11.2.0/../../../../include/c++/11.2.0/bits/vector.tcc:115:21: note: in instantiation of function template specialization 'std::allocator_traits<std::allocator<RegularNumber>>::construct<RegularNumber, int, Named<int, index_for_vector_of_RegularNumber> &, Named<int, index_for_vector_of_RegularNumber>>' requested here
_Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
^
<source>:115:19: note: in instantiation of function template specialization 'std::vector<RegularNumber>::emplace_back<int, Named<int, index_for_vector_of_RegularNumber> &, Named<int, index_for_vector_of_RegularNumber>>' requested here
regNumVec.emplace_back(*it - '0', leftRegNumIdx,
^
/opt/compiler-explorer/gcc-11.2.0/lib/gcc/x86_64-linux-gnu/11.2.0/../../../../include/c++/11.2.0/bits/stl_construct.h:94:5: note: candidate template ignored: substitution failure [with _Tp = RegularNumber, _Args = <int, Named<int, index_for_vector_of_RegularNumber> &, Named<int, index_for_vector_of_RegularNumber>>]: no matching constructor for initialization of 'RegularNumber'
construct_at(_Tp* __location, _Args&&... __args)
^
What doesn't Clang understand what seems fine according to the other two? Is this a bug, or my mistake?
Here's the "minimalized" code:
#include <algorithm>
#include <concepts>
#include <iostream>
#include <iterator>
#include <sstream>
#include <string>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
template <typename Type, typename Description>
class Named {
public:
using UnderlyingType = Type;
// clang-format off
constexpr Named()
noexcept(std::is_nothrow_default_constructible_v<Type>)
requires std::default_initializable<Type>
= default;
explicit constexpr Named(Type const &val)
noexcept(std::is_nothrow_copy_constructible_v<Type>)
requires std::copyable<Type>
: val_{val}
{}
explicit constexpr Named(Type&& val)
noexcept(std::is_nothrow_move_constructible_v<Type>)
requires std::movable<Type>
: val_{std::move(val)}
{}
constexpr operator Type() const&
noexcept(std::is_nothrow_copy_constructible_v<Type>)
requires std::copyable<Type>
{
return val_;
}
constexpr operator Type() const&&
noexcept(std::is_nothrow_move_constructible_v<Type>) // move assignable?
requires std::movable<Type>
{
return std::move(val_);
}
private:
Type val_;
};
// clang-format on
template <typename T>
auto readinputdata(std::istream &&is) {
auto data{std::vector<T>{}};
copy(std::istream_iterator<T>{is}, std::istream_iterator<T>{},
back_inserter(data));
return data;
}
static constexpr auto testData{
"[[[0,[5,8]],[[1,7],[9,6]]],[[4,[1,2]],[[1,4],2]]]\n"
"[[[5,[2,8]],4],[5,[[9,9],0]]]\n"
"[6,[[[6,2],[5,6]],[[7,6],[4,7]]]]\n"
"[[[6,[0,7]],[0,9]],[4,[9,[9,0]]]]\n"
"[[[7,[6,4]],[3,[1,3]]],[[[5,5],1],9]]\n"
"[[6,[[7,3],[3,2]]],[[[3,8],[5,7]],4]]\n"
"[[[[5,4],[7,7]],8],[[8,3],8]]\n"
"[[9,3],[[9,9],[6,[4,9]]]]\n"
"[[2,[[7,7],7]],[[5,8],[[9,3],[0,2]]]]\n"
"[[[[5,2],5],[8,[3,7]]],[[5,[7,5]],[4,4]]]\n"};
struct index_for_vector_of_Pair;
using PairIndex = Named<int, index_for_vector_of_Pair>;
struct index_for_vector_of_RegularNumber;
using RegNumIndex = Named<int, index_for_vector_of_RegularNumber>;
struct RegularNumber {
using IndexType = RegNumIndex;
int value{};
RegNumIndex leftIdx{}, rightIdx{};
};
using IndexVariant = std::variant<PairIndex, RegNumIndex>;
struct Pair {
using IndexType = PairIndex;
PairIndex parent{};
IndexVariant leftIdx{}, rightIdx{};
};
static auto pairVec{std::vector<Pair>{}};
static auto regNumVec{std::vector<RegularNumber>{}};
// used for converting the input strings to pairs
static auto leftRegNumIdx{RegNumIndex{-1}};
static constexpr auto invalid_index{-1};
template <typename Type>
auto get_last_index(std::vector<Type> const &vec) {
using Index = typename Type::IndexType;
return Index{static_cast<typename Index::UnderlyingType>(size(vec))};
}
auto generate_pair(std::string::const_iterator &it, PairIndex parent)
-> PairIndex;
auto generate_side(std::string::const_iterator &it, PairIndex pairIdx)
-> IndexVariant {
if (*it == '[') {
return generate_pair(it, pairIdx);
} else {
regNumVec.emplace_back(*it - '0', leftRegNumIdx, // error on this line
RegNumIndex{invalid_index});
auto const litIdx{get_last_index(regNumVec)};
if (leftRegNumIdx != invalid_index) {
regNumVec[leftRegNumIdx].rightIdx = litIdx;
}
leftRegNumIdx = litIdx;
return litIdx;
};
}
auto generate_pair(std::string::const_iterator &it,
PairIndex parent = PairIndex{invalid_index}) -> PairIndex {
pairVec.emplace_back();
auto const pairIdx{get_last_index(pairVec)};
auto &pair{pairVec.back()};
pair.parent = parent;
++it; // skip [
pair.leftIdx = generate_side(it, pairIdx);
++it; // skip comma
pair.rightIdx = generate_side(it, pairIdx);
++it; // skip ]
return pairIdx;
}
int main() {
auto const snailFishNumbers{readinputdata<std::string>( //
std::stringstream{testData} //
)};
auto const outerPairIdxs{[&] {
auto outerPairIdxs{std::vector<PairIndex>{}};
outerPairIdxs.reserve(size(snailFishNumbers));
transform(cbegin(snailFishNumbers), cend(snailFishNumbers),
back_inserter(outerPairIdxs), [](auto const &str) {
auto it{cbegin(str)}; // need lvalue
return generate_pair(it);
});
return outerPairIdxs;
}()};
return 0;
}
Your type is an aggregate
struct RegularNumber {
using IndexType = RegNumIndex;
int value{};
RegNumIndex leftIdx{}, rightIdx{};
};
which construct_at tries to initialize with round parenthesis
::new(std::declval<void*>()) T(std::declval<Args>()...).
However, an aggregate needs a {} initializer, until the compiler implements P0960 Allow initializing aggregates from a parenthesized list of values
And Clang just isn't there yet. Compiler support for C++20
Forward_iterator compiles fine!
#include <concepts>
#include <iterator>
// #include <iterator_concept>
using namespace std;
template<typename T>
// struct my_iterator : std::forward_iterator_tag{
struct my_iterator : std::bidirectional_iterator_tag {
T& operator*()const{
return *pointer;
}
my_iterator& operator++(){
++pointer;
return *this;
}
my_iterator operator++(int){
auto copy = *this;
++pointer;
return copy;
}
// my_iterator& operator--(){
// --pointer;
// return *this;
// }
// my_iterator operator--(int){
// auto copy = *this;
// --pointer;
// return copy;
// }
using difference_type = ptrdiff_t;
using value_type = T;
// auto operator<=>(const my_iterator&) const = default;
bool operator==(const my_iterator& other)const{
return pointer == other.pointer;
}
bool operator!=(const my_iterator& other)const{
return pointer != other.pointer;
}
private:
T*pointer;
};
// template<class T> constexpr bool always_false = false;
// static_assert(always_false<std::iter_reference_t<my_iterator<int>>>, "asd");
// typedef typename std::iter_reference_t<my_iterator<int>>::something_made_up X;
// static_assert(std::weakly_incrementable<my_iterator<int>>);
// static_assert(std::equality_comparable<my_iterator<int>>);
static_assert(std::forward_iterator<my_iterator<int>>);
// static_assert(std::bidirectional_iterator<my_iterator<int>>);
int main()
{
my_iterator<int> i1;
}
live snippet godbolt
but bidirectional does not. I tried g++ v10 and clang++ v11. Both give nearly the same compile time error.
#include <concepts>
#include <iterator>
using namespace std;
template<typename T>
// struct my_iterator : std::forward_iterator_tag{
struct my_iterator : std::bidirectional_iterator_tag {
T& operator*()const{
return *pointer;
}
my_iterator& operator++(){
++pointer;
return *this;
}
my_iterator operator++(int){
auto copy = *this;
++pointer;
return copy;
}
my_iterator& operator--(){
--pointer;
return *this;
}
my_iterator operator--(int){
auto copy = *this;
--pointer;
return copy;
}
using difference_type = ptrdiff_t;
using value_type = T;
// auto operator<=>(const my_iterator&) const = default; // Why this does not work
bool operator==(const my_iterator& other)const{
return pointer == other.pointer;
}
bool operator!=(const my_iterator& other)const{
return pointer != other.pointer;
}
private:
T*pointer;
};
// template<class T> constexpr bool always_false = false;
// static_assert(always_false<std::iter_reference_t<my_iterator<int>>>, "asd");
// typedef typename std::iter_reference_t<my_iterator<int>>::something_made_up X;
// static_assert(std::weakly_incrementable<my_iterator<int>>);
// static_assert(std::equality_comparable<my_iterator<int>>);
static_assert(std::forward_iterator<my_iterator<int>>);
// static_assert(std::bidirectional_iterator<my_iterator<int>>);
int main()
{
my_iterator<int> i1;
}
live
In file included from <source>:2:
In file included from /opt/compiler-explorer/gcc-10.2.0/lib/gcc/x86_64-linux-gnu/10.2.0/../../../../include/c++/10.2.0/iterator:61:
In file included from /opt/compiler-explorer/gcc-10.2.0/lib/gcc/x86_64-linux-gnu/10.2.0/../../../../include/c++/10.2.0/bits/stl_iterator_base_types.h:71:
/opt/compiler-explorer/gcc-10.2.0/lib/gcc/x86_64-linux-gnu/10.2.0/../../../../include/c++/10.2.0/bits/iterator_concepts.h:409:52: error: ambiguous partial specializations of '__cat<my_iterator<int>>'
using iterator_category = typename __detail::__cat<_Iterator>::type;
^
/opt/compiler-explorer/gcc-10.2.0/lib/gcc/x86_64-linux-gnu/10.2.0/../../../../include/c++/10.2.0/bits/stl_iterator_base_types.h:178:14: note: in instantiation of template class 'std::__iterator_traits<my_iterator<int>, void>' requested here
: public __iterator_traits<_Iterator> { };
^
/opt/compiler-explorer/gcc-10.2.0/lib/gcc/x86_64-linux-gnu/10.2.0/../../../../include/c++/10.2.0/bits/iterator_concepts.h:182:71: note: in instantiation of template class 'std::iterator_traits<my_iterator<int>>' requested here
= __is_base_of(__iterator_traits<_Iter, void>, iterator_traits<_Iter>);
^
/opt/compiler-explorer/gcc-10.2.0/lib/gcc/x86_64-linux-gnu/10.2.0/../../../../include/c++/10.2.0/bits/iterator_concepts.h:182:4: note: while substituting template arguments into constraint expression here
= __is_base_of(__iterator_traits<_Iter, void>, iterator_traits<_Iter>);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/lib/gcc/x86_64-linux-gnu/10.2.0/../../../../include/c++/10.2.0/bits/iterator_concepts.h:189:16: note: while checking the satisfaction of concept '__primary_traits_iter<my_iterator<int>>' requested here
requires __primary_traits_iter<_Iter>
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/lib/gcc/x86_64-linux-gnu/10.2.0/../../../../include/c++/10.2.0/bits/iterator_concepts.h:189:16: note: while substituting template arguments into constraint expression here
requires __primary_traits_iter<_Iter>
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/lib/gcc/x86_64-linux-gnu/10.2.0/../../../../include/c++/10.2.0/bits/iterator_concepts.h:195:7: note: (skipping 10 contexts in backtrace; use -ftemplate-backtrace-limit=0 to see all)
using __iter_traits = typename __iter_traits_impl<_Iter, _Tp>::type;
^
/opt/compiler-explorer/gcc-10.2.0/lib/gcc/x86_64-linux-gnu/10.2.0/../../../../include/c++/10.2.0/bits/iterator_concepts.h:564:30: note: while checking the satisfaction of concept 'input_or_output_iterator<my_iterator<int>>' requested here
concept input_iterator = input_or_output_iterator<_Iter>
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/lib/gcc/x86_64-linux-gnu/10.2.0/../../../../include/c++/10.2.0/bits/iterator_concepts.h:564:30: note: while substituting template arguments into constraint expression here
concept input_iterator = input_or_output_iterator<_Iter>
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/lib/gcc/x86_64-linux-gnu/10.2.0/../../../../include/c++/10.2.0/bits/iterator_concepts.h:575:32: note: while checking the satisfaction of concept 'input_iterator<my_iterator<int>>' requested here
concept forward_iterator = input_iterator<_Iter>
^~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/lib/gcc/x86_64-linux-gnu/10.2.0/../../../../include/c++/10.2.0/bits/iterator_concepts.h:575:32: note: while substituting template arguments into constraint expression here
concept forward_iterator = input_iterator<_Iter>
^~~~~~~~~~~~~~~~~~~~~
<source>:52:15: note: while checking the satisfaction of concept 'forward_iterator<my_iterator<int>>' requested here
static_assert(std::forward_iterator<my_iterator<int>>);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-10.2.0/lib/gcc/x86_64-linux-gnu/10.2.0/../../../../include/c++/10.2.0/bits/iterator_concepts.h:369:14: note: partial specialization matches [with _Iter = my_iterator<int>]
struct __cat<_Iter>
^
/opt/compiler-explorer/gcc-10.2.0/lib/gcc/x86_64-linux-gnu/10.2.0/../../../../include/c++/10.2.0/bits/iterator_concepts.h:375:14: note: partial specialization matches [with _Iter = my_iterator<int>]
struct __cat<_Iter>
^
<source>:52:1: error: static_assert failed
static_assert(std::forward_iterator<my_iterator<int>>);
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:52:20: note: because 'my_iterator<int>' does not satisfy 'forward_iterator'
static_assert(std::forward_iterator<my_iterator<int>>);
^
/opt/compiler-explorer/gcc-10.2.0/lib/gcc/x86_64-linux-gnu/10.2.0/../../../../include/c++/10.2.0/bits/iterator_concepts.h:575:32: note: because 'my_iterator<int>' does not satisfy 'input_iterator'
concept forward_iterator = input_iterator<_Iter>
^
/opt/compiler-explorer/gcc-10.2.0/lib/gcc/x86_64-linux-gnu/10.2.0/../../../../include/c++/10.2.0/bits/iterator_concepts.h:564:30: note: because 'my_iterator<int>' does not satisfy 'input_or_output_iterator'
concept input_iterator = input_or_output_iterator<_Iter>
^
/opt/compiler-explorer/gcc-10.2.0/lib/gcc/x86_64-linux-gnu/10.2.0/../../../../include/c++/10.2.0/bits/iterator_concepts.h:544:5: note: because 'my_iterator<int>' does not satisfy 'weakly_incrementable'
&& weakly_incrementable<_Iter>;
^
/opt/compiler-explorer/gcc-10.2.0/lib/gcc/x86_64-linux-gnu/10.2.0/../../../../include/c++/10.2.0/bits/iterator_concepts.h:198:7: note: because 'typename iter_difference_t<_Iter>' would be invalid: no type named 'difference_type' in 'std::iterator_traits<my_iterator<int>>'
using __iter_diff_t = typename
^
2 errors generated.
Compiler returned: 1
x86-64 clang 11.0.0
1
More questions:
Why auto operator<=>(const my_iterator&) const = default; does not provide == and !=?
May be it does but static_assert(std::forward_iterator<my_iterator<int>>); want operator== to be explicit.
Is there a way to explicitly say this struct should meet the requirements and constraints of the concept like
template<typename T>
struct my_iterator implements forward_iterator {};
Currently I need to check it with static_assert(std::forward_iterator<my_iterator<int>>);.
This:
struct my_iterator : std::bidirectional_iterator_tag {
is not how you indicate the iterator category for an iterator. You don't inherit from a tag type, you have it as a member type alias.
Like so:
struct iterator {
using iterator_category = std::bidirectional_iterator_tag;
};
Although this shouldn't actually be necessary anyway. Even without this tag (and definitely without inheritance), this iterator should count as a bidirectional iterator since you satisfy the other requirements. It's actually a libstdc++ bug that it doesn't compile at that point.
Why auto operator<=>(const my_iterator&) const = default; does not provide == and !=?
It does. It's just that you were inheriting from a type (the iterator tag) that was not comparable, so the defaulted comparisons were defined as deleted.
Is there a way to explicitly say this struct should meet the requirements and constraints of the concept
There is not.
The following code compiles perfectly if:
I don't include <iostream> or
I name operator== as alp::operator==.
I suppose there is a problem with <iostream> and operator==, but I don't know what.
I compile the code with gcc 7.3.0, clang++-6.0 and goldbolt. Always the same error.
The problem is that the compiler is trying to cast the parameters of operator== to const_iterator, but why? (I suppose the compiler doesn't see my version of operator==, and looks for other versions).
#include <vector>
#include <iostream> // comment and compile
namespace alp{
template <typename It_base>
struct Iterator {
using const_iterator = Iterator<typename It_base::const_iterator>;
operator const_iterator() { return const_iterator{}; }
};
template <typename It_base>
bool operator==(const Iterator<It_base>& x, const Iterator<It_base>& y)
{ return true;}
}// namespace
struct Func{
int& operator()(int& p) const {return p;}
};
template <typename It, typename View>
struct View_iterator_base{
using return_type = decltype(View{}(*It{}));
using const_iterator =
View_iterator_base<std::vector<int>::const_iterator, Func>;
};
using view_it =
alp::Iterator<View_iterator_base<std::vector<int>::iterator, Func>>;
int main()
{
view_it p{};
view_it z{};
bool x = operator==(z, p); // only compiles if you remove <iostream>
bool y = alp::operator==(z,p); // always compile
}
Error message:
yy.cpp: In instantiation of ‘struct View_iterator_base<__gnu_cxx::__normal_iterator<const int*, std::vector<int> >, Func>’:
yy.cpp:9:73: required from ‘struct alp::Iterator<View_iterator_base<__gnu_cxx::__normal_iterator<const int*, std::vector<int> >, Func> >’
yy.cpp:44:29: required from here
yy.cpp:28:42: error: no match for call to ‘(Func) (const int&)’
using return_type = decltype(View{}(*It{}));
~~~~~~^~~~~~~
yy.cpp:22:10: note: candidate: int& Func::operator()(int&) const <near match>
int& operator()(int& p) const {return p;}
^~~~~~~~
yy.cpp:22:10: note: conversion of argument 1 would be ill-formed:
yy.cpp:28:42: error: binding reference of type ‘int&’ to ‘const int’ discards qualifiers
using return_type = decltype(View{}(*It{}));
~~~~~~^~~~~~~
I've made a more minimal test case here: https://godbolt.org/z/QQonMG .
The relevant details are:
A using type alias does not instantiate a template. So for example:
template<bool b>
struct fail_if_true {
static_assert(!b, "template parameter must be false");
};
using fail_if_used = fail_if_true<true>;
will not cause a compile time error (if fail_if_used isn't used)
ADL also inspects template parameter classes. In this case, std::vector<int>::iterator is __gnu_cxx::__normal_iterator<const int*, std::vector<int> >, Func>, which has a std::vector<int> in it's template. So, operator== will check in the global namespace (always), alp (As alp::Iterator is in alp), __gnu_cxx and std.
Your View_iterator_base::const_iterator is invalid. View_iterator_base::const_interator::result_type is defined as decltype(Func{}(*std::vector<int>::const_iterator{})). std::vector<int>::const_iterator{} will be a vectors const iterator, so *std::vector<int>::const_iterator{} is a const int&. Func::operator() takes an int&, so this means that the expression is invalid. But it won't cause a compile time error if not used, for the reasons stated above. This means that your conversion operator is to an invalid type.
Since you don't define it as explicit, the conversion operator (To an invalid type) will be used to try and match it to the function parameters if they don't already match. Obviously this will finally instantiate the invalid type, so it will throw a compile time error.
My guess is that iostream includes string, which defines std::operator== for strings.
Here's an example without the std namespace: https://godbolt.org/z/-wlAmv
// Avoid including headers for testing without std::
template<class T> struct is_const { static constexpr const bool value = false; } template<class T> struct is_const<const T> { static constexpr const bool value = true; }
namespace with_another_equals {
struct T {};
bool operator==(const T&, const T&) {
return true;
}
}
namespace ns {
template<class T>
struct wrapper {
using invalid_wrapper = wrapper<typename T::invalid>;
operator invalid_wrapper() {}
};
template<class T>
bool operator==(const wrapper<T>&, const wrapper<T>&) {
return true;
}
}
template<class T>
struct with_invalid {
static_assert(!is_const<T>::value, "Invalid if const");
using invalid = with_invalid<const T>;
};
template<class T>
void test() {
using wrapped = ns::wrapper<with_invalid<T>>;
wrapped a;
wrapped b;
bool x = operator==(a, b);
bool y = ns::operator==(a, b);
}
template void test<int*>();
// Will compile if this line is commented out
template void test<with_another_equals::T>();
Note that just declaring operator const_iterator() should instantiate the type. But it doesn't because it is within templates. My guess is that it is optimised out (where it does compile because it's unused) before it can be checked to show that it can't compile (It doesn't even warn with -Wall -pedantic that it doesn't have a return statement in my example).
I would like to use BOOST_FOREACH with one of legacy container type in my code base which I cannot change.
I have the following methods defined on that type:
.length() returning the current number of elements in the container
.operator[](unsigned i) returning a reference to element under index i
I know that I need to make my container type to satisfy Single Pass Range Concept as the boost documentation suggest but I am somehow lost since I cannot modify the type.
Any hints/solutions will be useful.
EDIT
I tried to go with suggestion but this code
struct wrapper
{
struct iterator : std::iterator<std::forward_iterator_tag, ObservationReport>
{
iterator(ObservationReportList* p, int i) : pnt_(p), i_(i) {}
ObservationReport& operator*() {
return (*pnt_)[i_];
}
iterator& operator++() {
++i_;
return *this;
}
bool operator==(const iterator& r) const {
return i_ == r.i_;
}
bool operator!=(const iterator& r) const {
return i_ != r.i_;
}
ObservationReportList* pnt_;
int i_;
};
wrapper(ObservationReportList & n)
: begin_( boost::addressof(n), 0 )
, end_( boost::addressof(n), n.length() )
{}
iterator begin() { return begin_; }
iterator end() { return end_; }
iterator begin_, end_;
};
// usage
ObservationReportList reportList;
// filling reportList
BOOST_FOREACH(ObservationReport o, wrapper(reportList))
{
}
gives me the following compiler errors:
In file included from /usr/include/boost159/boost/foreach.hpp:71:0,
from test/src/MessageFillerTest.cpp:18:
/usr/include/boost159/boost/mpl/eval_if.hpp: In instantiation of ‘struct boost::mpl::eval_if<mpl_::bool_<true>, boost::range_const_iterator<wrapper, void>, boost::range_mutable_iterator<wrapper, void> >’:
/usr/include/boost159/boost/foreach.hpp:359:13: required from ‘struct boost::foreach_detail_::foreach_iterator<wrapper, mpl_::bool_<true> >’
/usr/include/boost159/boost/foreach.hpp:679:1: required by substitution of ‘template<class T> boost::foreach_detail_::auto_any<typename boost::foreach_detail_::foreach_iterator<T, mpl_::bool_<true> >::type> boost::foreach_detail_::begin(
boost::foreach_detail_::auto_any_t, boost::foreach_detail_::type2type<T, mpl_::bool_<true> >*, bool*) [with T = wrapper]’
test/src/MessageFillerTest.cpp:206:3: required from here
/usr/include/boost159/boost/mpl/eval_if.hpp:38:31: error: no type named ‘type’ in ‘boost::mpl::eval_if<mpl_::bool_<true>, boost::range_const_iterator<wrapper, void>, boost::range_mutable_iterator<wrapper, void> >::f_ {aka struct boost::ran
ge_const_iterator<wrapper, void>}’
typedef typename f_::type type;
^
In file included from test/src/MessageFillerTest.cpp:18:0:
test/src/MessageFillerTest.cpp: In member function ‘virtual void MessageFiller_XXX_Test::TestBody()’:
/usr/include/boost159/boost/foreach.hpp:1020:39: error: no matching function for call to ‘begin(const boost::foreach_detail_::auto_any_base&, boost::foreach_detail_::type2type<wrapper, mpl_::bool_<true> >*, bool*)’
, BOOST_FOREACH_SHOULD_COPY(COL))
^
/usr/include/boost159/boost/foreach.hpp:1101:77: note: in expansion of macro ‘BOOST_FOREACH_BEGIN’
if (boost::foreach_detail_::auto_any_t BOOST_FOREACH_ID(_foreach_cur) = BOOST_FOREACH_BEGIN(COL)) {} else \
^
test/src/MessageFillerTest.cpp:206:3: note: in expansion of macro ‘BOOST_FOREACH’
BOOST_FOREACH(ObservationReport o, wrapper(reportList))
^
/usr/include/boost159/boost/foreach.hpp:660:1: note: candidate: template<class T, class C> boost::foreach_detail_::auto_any<typename boost::foreach_detail_::foreach_iterator<T, C>::type> boost::foreach_detail_::begin(boost::foreach_detail_
::auto_any_t, boost::foreach_detail_::type2type<T, C>*, mpl_::true_*)
begin(auto_any_t col, type2type<T, C> *, boost::mpl::true_ *) // rvalue
^
/usr/include/boost159/boost/foreach.hpp:660:1: note: template argument deduction/substitution failed:
/usr/include/boost159/boost/foreach.hpp:964:46: note: cannot convert ‘boost::foreach_detail_::should_copy_impl(((mpl_::bool_<false>*)0u), ((mpl_::bool_<false>*)0u), (& _foreach_is_rvalue206))’ (type ‘bool*’) to type ‘mpl_::true_* {aka mp
l_::bool_<true>*}’
(boost::foreach_detail_::should_copy_impl( \
^
/usr/include/boost159/boost/foreach.hpp:1020:9: note: in expansion of macro ‘BOOST_FOREACH_SHOULD_COPY’
, BOOST_FOREACH_SHOULD_COPY(COL))
^
/usr/include/boost159/boost/foreach.hpp:1101:77: note: in expansion of macro ‘BOOST_FOREACH_BEGIN’
if (boost::foreach_detail_::auto_any_t BOOST_FOREACH_ID(_foreach_cur) = BOOST_FOREACH_BEGIN(COL)) {} else \
^
test/src/MessageFillerTest.cpp:206:3: note: in expansion of macro ‘BOOST_FOREACH’
BOOST_FOREACH(ObservationReport o, wrapper(reportList))
^
/usr/include/boost159/boost/foreach.hpp:668:1: note: candidate: template<class T, class C> boost::foreach_detail_::auto_any<typename boost::foreach_detail_::foreach_iterator<T, C>::type> boost::foreach_detail_::begin(boost::foreach_detail_
::auto_any_t, boost::foreach_detail_::type2type<T, C>*, mpl_::false_*)
begin(auto_any_t col, type2type<T, C> *, boost::mpl::false_ *) // lvalue
^
/usr/include/boost159/boost/foreach.hpp:668:1: note: template argument deduction/substitution failed:
/usr/include/boost159/boost/foreach.hpp:964:46: note: cannot convert ‘boost::foreach_detail_::should_copy_impl(((mpl_::bool_<false>*)0u), ((mpl_::bool_<false>*)0u), (& _foreach_is_rvalue206))’ (type ‘bool*’) to type ‘mpl_::false_* {aka m
pl_::bool_<false>*}’
(boost::foreach_detail_::should_copy_impl( \
^
/usr/include/boost159/boost/foreach.hpp:1020:9: note: in expansion of macro ‘BOOST_FOREACH_SHOULD_COPY’
, BOOST_FOREACH_SHOULD_COPY(COL))
^
/usr/include/boost159/boost/foreach.hpp:1101:77: note: in expansion of macro ‘BOOST_FOREACH_BEGIN’
if (boost::foreach_detail_::auto_any_t BOOST_FOREACH_ID(_foreach_cur) = BOOST_FOREACH_BEGIN(COL)) {} else \
^
test/src/MessageFillerTest.cpp:206:3: required from here
/usr/include/boost159/boost/foreach.hpp:768:1: error: no type named ‘type’ in ‘struct boost::foreach_detail_::foreach_reference<wrapper, mpl_::bool_<true> >’
A wrapper can provide the required interface.
Note that in this example, operator[] of nasty_thing returns a reference to an int in an array of consecutive ints.
It this is not the case, we need to make the wrapper work in terms of an index.
#include <utility>
#include <memory>
//
// a nasty thing that contains consecutive ints
struct nasty_thing
{
int& operator[](int i);
int length();
};
// because we are dealing in consecutive ints, we can approximate an iterator with a pointer
struct wrapper
{
using iterator = int*;
wrapper(nasty_thing& n)
: begin_(std::addressof(n[0]))
, end_(begin_ + n.length())
{}
iterator begin() const { return begin_; }
iterator end() const { return end_; }
iterator begin_, end_;
};
extern nasty_thing& nt;
int main()
{
// simulate BOOST_FOREACH
for (auto& i : wrapper(nt))
{
}
}
This is the version in which nasty_thing does not represent contiguous memory:
#include <utility>
#include <memory>
#include <boost/foreach.hpp>
//
// a nasty thing that contains consecutive ints
struct nasty_thing
{
int& operator[](int i);
int length();
};
struct wrapper
{
struct iterator : std::iterator<std::forward_iterator_tag, int>
{
iterator(nasty_thing* p, int i) : pnt_(p), i_(i) {}
int& operator*() const {
return (*pnt_)[i_];
}
iterator& operator++() {
++i_;
return *this;
}
bool operator==(const iterator& r) const {
return i_ == r.i_;
}
bool operator!=(const iterator& r) const {
return i_ != r.i_;
}
nasty_thing* pnt_;
int i_;
};
// needed by BOOST_FOREACH
using const_iterator = iterator;
wrapper(nasty_thing& n)
: begin_{ std::addressof(n), 0 }
, end_{std::addressof(n), n.length()}
{}
iterator begin() const { return begin_; }
iterator end() const { return end_; }
iterator begin_, end_;
};
extern nasty_thing& nt;
int main()
{
// simulate BOOST_FOREACH
BOOST_FOREACH(auto& i, wrapper(nt))
{
}
}
There are docs:
http://www.boost.org/doc/libs/1_61_0/doc/html/foreach/extensibility.html
That walk you through extending the range concept to a custom container without modfying the container.
Create a range_begin and range_end free functions that take your container type in th container's namespace.
In namespace boost, specialize the traits class(es) that tells boost the iterator type:
template<> struct range_mutable_iterator< your::type::here > {
typedef your_iterator_type type;
};
template<> struct range_const_iterator< your::type::here > {
typedef your_iterator_type type;
};
And done.
This could probably be done with specializing boost::range_iterator<X>::type and boost::range_iterator<const X>::type and direct overloads of boost::begin/boost::end or ADL begin/end overloads, but I thought I'd use the documented extensibility path I found.
I will just post it for reference as Richard Hodges got it almost right.
struct wrapper
{
struct iterator : std::iterator<std::forward_iterator_tag, ObservationReport>
{
iterator(ObservationReportList* p, int i) : pnt_(p), i_(i) {}
ObservationReport& operator*() {
return (*pnt_)[i_];
}
iterator& operator++() {
++i_;
return *this;
}
bool operator==(const iterator& r) const {
return i_ == r.i_;
}
bool operator!=(const iterator& r) const {
return i_ != r.i_;
}
ObservationReportList* pnt_;
int i_;
};
typedef iterator const_iterator; // essential (in one way or another)
// http://www.boost.org/doc/libs/1_61_0/libs/range/doc/html/range/concepts/single_pass_range.html
wrapper(ObservationReportList & n)
: begin_( boost::addressof(n), 0 )
, end_( boost::addressof(n), n.length() )
{}
iterator begin() { return begin_; }
iterator end() { return end_; }
iterator begin() const { return begin_; } // essential for Single Pass concept
iterator end() const { return end_; } // essential for Single Pass concept
iterator begin_, end_;
};
// usage
ObservationReportList reportList;
// filling reportList
BOOST_FOREACH(ObservationReport o, wrapper(reportList))
{
}
So basically we missed const_iterator type definition (here only a typedef) and const versions of begin() and end()