I made this input iterator
template <typename T>
struct input_iter: base_it<T> {
private:
typename base_it<T>::pointer _ptr;
public:
constexpr input_iter<T>() = default;
constexpr explicit input_iter<T>(typename base_it<T>::pointer ptr = nullptr)
: _ptr { ptr } {}
constexpr ~input_iter<T>() = default;
constexpr input_iter<T>(const input_iter<T>& other) = default;
constexpr input_iter<T>(input_iter<T>&& other) noexcept = default;
[[nodiscard]]
constexpr auto operator=(typename base_it<T>::pointer ptr) -> input_iter<T>& {
_ptr = ptr; return *this;
}
constexpr auto operator=(const input_iter<T>&) -> input_iter<T>& = default;
constexpr auto operator=(input_iter<T>&&) noexcept -> input_iter<T>& = default;
[[nodiscard]]
constexpr auto operator*() const noexcept -> const typename base_it<T>::reference {
return *_ptr;
}
[[nodiscard]]
constexpr auto operator->() const noexcept -> const typename base_it<T>::pointer {
return _ptr;
}
constexpr auto operator++() noexcept -> input_iter& {
++this-> _ptr;
return *this;
}
constexpr void operator++(int) noexcept {
++(*this);
}
[[nodiscard]]
constexpr friend auto operator==(const input_iter& lhs, const input_iter& rhs) noexcept -> bool {
return lhs._ptr == rhs._ptr;
}
[[nodiscard]]
constexpr friend auto operator!=(const input_iter& lhs, const input_iter& rhs) noexcept -> bool {
return not (lhs == rhs);
}
};
Where you must assume (for now) that base_iter is std::iterator.
Well, one mandatory requirement for an input_iterator is that must be indirectly_readable. Since my input_iter doesn't belong to any concrete datastructure, and is in a module by itself, because I want to make it available for containers or ranges which is elements are stored in contiguous memory locations (but that's an story for another SO post) I would like to constraint the operation of writing things to the underlying container or range. So, my idea is the following:
template <typename T>
using base_it = std::iterator<std::input_iterator_tag, const T>;
Note the const T, not T in the alias
So whenever I try to write an stamement of this kind:
collections::Array arr = collections::Array<int, 5>{1, 2, 3, 4, 5};
auto it_begin = arr.begin();
*it_begin = 7;
I am receving the error that I want, at compile time!
.\zero\tests\iterators\legacy\legacy_iterator_tests.cpp:47:28: error: cannot assign to return value because function
'operator*' returns a const value
*it_begin = 7;
Fine. But...
I am doing idiomatically?
Exists another better alternative?
How could I write an static_assert and check that that expression doesn't compiles? I've seen complicated stuff going on with lambdas in the else block on an if constexpr, and some more using SFINAE, but I am not able to write it by myself. Can someone explain how to write it?
Code below is what I've tried so far, without obviously, no success. Take it as a meta-idea.
if constexpr (some_cond)
static_assert(
*it_begin = 7,
"Wait... this is compiling! This shouldn't happen, since an input iterator musn't be able to performn write operations");
As for the static assert: why not just use a concept if you're using C++20?
{*d = from} is the expression that has to compile while
-> std::convertible_to<decltype(*d)>; part is the optional type of that expression. It can be made to match the exact type with same_as, or can be just removed in case the type is irrelevant.
#include <concepts>
#include <vector> //just for demonstration purposes
template<typename Dereferencable, typename From>
concept DereferencedAssignable = requires(Dereferencable d, From from)
{
{*d = from} -> std::convertible_to<decltype(*d)>; //or std::same_as, depending on one's needs
};
static_assert(DereferencedAssignable<int*, int>);
static_assert(not DereferencedAssignable<const int*, int>);
static_assert(DereferencedAssignable<std::vector<double>::iterator, double>);
static_assert(not DereferencedAssignable<std::vector<double>::const_iterator, double>);
https://godbolt.org/z/8s44Gb3ME
As for being idiomatic:
I would opt for returning a copy of the previous value in the the postfix incrementation operator, but that might have been just a minor oversight here.
Related
I have the following simplified code representing a range of integers that I want to use with various std algorithms. I am trying to update my code to use C++20's ranges versions of the algorithms so I can delete all of the begin() and end() calls. In the below code, std::any_of works with my container and iterator, but std::ranges::any_of does not.
#include <iostream>
#include <algorithm>
class Number_Iterator {
public:
using iterator_category = std::input_iterator_tag;
using value_type = int;
using difference_type = int;
using pointer = int*;
using reference = int&;
Number_Iterator(int start) noexcept : value(start) {}
Number_Iterator& operator++() noexcept { ++value; return *this; }
bool operator==(const Number_Iterator& other) const noexcept = default;
int operator*() const noexcept { return value; }
private:
int value;
};
class Numbers {
public:
Numbers(int begin, int end) noexcept : begin_value(begin), end_value(end) {}
Number_Iterator begin() const noexcept { return {begin_value}; }
Number_Iterator end() const noexcept { return {end_value}; }
private:
int begin_value;
int end_value;
};
int main() {
const auto set = Numbers(1, 10);
const auto multiple_of_three = [](const auto n) { return n % 3 == 0; };
// Compiles and runs correctly
if(std::any_of(set.begin(), set.end(), multiple_of_three)) {
std::cout << "Contains multiple of three.\n";
}
// Does not compile
if(std::ranges::any_of(set, multiple_of_three)) {
std::cout << "Contains multiple of three.\n";
}
return 0;
}
When I try to compile the above code, I get the following error messages from Visual Studio 2019 (16.11.15) with the flag /std:c++20:
Source.cpp(42,21): error C2672: 'operator __surrogate_func': no matching overloaded function found
Source.cpp(42,7): error C7602: 'std::ranges::_Any_of_fn::operator ()': the associated constraints are not satisfied
1>C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\include\algorithm(1191): message : see declaration of 'std::ranges::_Any_of_fn::operator ()'
I have tried looking at the std::ranges::_Any_of_fn::operator() declaration, but I find myself more confused by that.
What am I missing to get the std::ranges algorithms to work with my container?
For the curious, what I'm actually iterating over are squares on a chess board, but those are represented by integers, so the difference from the above code isn't so great.
To use your range with any_of it must satisfy the input_range concept:
template< class T >
concept input_range =
ranges::range<T> && std::input_iterator<ranges::iterator_t<T>>;
Then via the input_iterator concept:
template<class I>
concept input_iterator =
std::input_or_output_iterator<I> &&
std::indirectly_readable<I> &&
requires { typename /*ITER_CONCEPT*/<I>; } &&
std::derived_from</*ITER_CONCEPT*/<I>, std::input_iterator_tag>;
and via the input_or_output_iterator concept
template <class I>
concept input_or_output_iterator =
requires(I i) {
{ *i } -> /*can-reference*/;
} &&
std::weakly_incrementable<I>;
you land in the weakly_incrementable concept:
template<class I>
concept weakly_incrementable =
std::movable<I> &&
requires(I i) {
typename std::iter_difference_t<I>;
requires /*is-signed-integer-like*/<std::iter_difference_t<I>>;
{ ++i } -> std::same_as<I&>; // pre-increment
i++; // post-increment
};
in which you see that the iterator must have both the pre-increment and post-increment versions of operator++.
The iterator must also be default constructible because std::ranges::end creates a sentinel:
template< class T >
requires /* ... */
constexpr std::sentinel_for<ranges::iterator_t<T>> auto end( T&& t );
And sentinel_for
template<class S, class I>
concept sentinel_for =
std::semiregular<S> &&
std::input_or_output_iterator<I> &&
__WeaklyEqualityComparableWith<S, I>;
requires it to satisfy semiregular:
template <class T>
concept semiregular = std::copyable<T> && std::default_initializable<T>;
But without being default constructible, this substitution will fail:
template < class T >
concept default_initializable = std::constructible_from<T> && requires { T{}; } && ...
Apparently, the std::ranges algorithms require two more methods in the iterator: a default constructor and a post-increment operator (return value optional). Adding these methods allows the code to compile and run correctly:
Number_Iterator() noexcept : value(-1) {}
void operator++(int) noexcept { ++value; }
The title of this question used to be: Are there practical advantages to creating an iterator class compared to returning raw pointers from begin and end functions?
Recently I have been working on a code base which uses MFC and objects such as CArray<T, U>.
Some parts of new code which has been written make use of the STL and <algorithm> library.
For example
CArray<int int> carray;
carray // do stuff
std::vector<int> stlvector(begin(carray), end(carray));
stlvector.dostuff() // do stuff
I recently asked a question about creating iterators for a class such as CArray, which I do not have access to.
I now have some further questions about this. The first question can be found here. Here is my second question:
Should the begin and end functions return raw pointers or iterators?
In the linked question above, an example was provided as an answer which returns raw pointers. This answer was very similar to the implementation I used.
template<typename T, typename U>
auto begin(const CArray<T, U> &array>)
{
return &array[0];
}
template<typename T, typename U>
auto end(const CArray<T, U> &array>)
{
return (&array[array.GetCount() - 1]) + 1;
}
These functions return raw pointers. However I attempted to implement an iterator solution. So far I have not been successful.
The main reference which I used during my research can be found here:
https://internalpointers.com/post/writing-custom-iterators-modern-cpp
First attempt
This is the first attempt that I made in finding a solution.
You can play with this code here.
#include <iostream>
#include <iterator>
#include <algorithm>
template <typename U>
class CArrayForwardIt
{
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = U;
using pointer = U*;
using reference = U&;
public:
CArrayForwardIt(pointer ptr)
: m_ptr(ptr)
{
}
// = default?
//CArrayForwardIt(CArrayForwardIt<U> other)
// : m_ptr(ptr)
// {
// }
reference operator*() const
{
return *m_ptr;
}
// what does this do, don't understand why operator-> is needed
// or why it returns a U* type
pointer operator->()
{
return m_ptr;
}
CArrayForwardIt& operator++()
{
++ m_ptr;
return *this;
}
CArrayForwardIt operator++(int)
{
CArrayForwardIt tmp(*this);
++ (*this);
return tmp;
}
friend bool operator==(const CArrayForwardIt& lhs, const CArrayForwardIt& rhs)
{
return lhs.m_ptr == rhs.m_ptr;
}
friend bool operator!=(const CArrayForwardIt& lhs, const CArrayForwardIt& rhs)
{
return !(lhs == rhs);
}
private:
pointer m_ptr;
};
template<typename T, typename U>
auto begin(const CArray<T, U> &array)
{
return CArrayForwardIt<U>(&array[0]);
}
template<typename T, typename U>
auto end(const CArray<T, U> &array)
{
return CArrayForwardIt<U>((&array[array.GetCount() - 1]) + 1);
}
int main()
{
CArray<int, int> c;
// do something to c
std::vector<int> v(begin(c), end(c));
return 0;
}
This is what happens when I try to compile this (with Visual Studio 2019 Pro).
no instance of constructor "std::vector<_Ty, _Alloc>::vector [with _Ty=int, _Alloc=std::allocator<int>]" matches argument list
'<function-style-cast>': cannot convert from 'contt TYPE*' to 'std::CArrayForwardIt<U>'
'std::vector<int, std::allocator<int>>::vector(std::vector<int, std::allocator<int>> &&, const _Alloc &) noexcept(<expr>)': cannot convert from argument 1 from 'void' to 'const unsigned int'
Being more familiar with gcc, I have little knowledge of how to understand this.
Second attempt
I made another two further attempts at this but they were quite similar.
One was to change my class CArrayForwardIt to inherit from iterator<std::forward_iterator_tag, std::ptrdiff_t, U, U*, U&>, and to remove the using... lines at the top of the class. This didn't seem to get me any closer to a solution.
In addition, I looked at the constructor definition for std::vector. See here.
I may be misunderstanding here, but it looks like std::vector requires a InputIt type argument.
Therefore I tried to change my class to be something like this:
#include <iostream>
#include <iterator>
#include <algorithm>
template <typename U>
class forward_iterator
{
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = U;
using pointer = U*;
using reference = U&;
public:
forward_iterator(pointer ptr)
: m_ptr(ptr)
{
}
// = default?
//forward_iterator(forward_iterator<U> other)
// : m_ptr(ptr)
// {
// }
reference operator*() const
{
return *m_ptr;
}
// what does this do, don't understand why operator-> is needed
// or why it returns a U* type
pointer operator->()
{
return m_ptr;
}
forward_iterator& operator++()
{
++ m_ptr;
return *this;
}
forward_iterator operator++(int)
{
forward_iterator tmp(*this);
++ (*this);
return tmp;
}
friend bool operator==(const forward_iterator& lhs, const forward_iterator& rhs)
{
return lhs.m_ptr == rhs.m_ptr;
}
friend bool operator!=(const forward_iterator& lhs, const forward_iterator& rhs)
{
return !(lhs == rhs);
}
private:
pointer m_ptr;
};
template<typename T, typename U>
auto begin(const CArray<T, U> &array)
{
return forward_iterator<U>(&array[0]);
}
template<typename T, typename U>
auto end(const CArray<T, U> &array)
{
return forward_iterator<U>((&array[array.GetCount() - 1]) + 1);
}
int main()
{
CArray<int, int> c;
// do something to c
std::vector<int> v(begin(c), end(c));
return 0;
}
This, perhaps unsurprisingly, did not compile either. At this point I became confused. std::vector appears to demand an InputIt type, which forward_iterator should work for, but it doesn't seem to make sense to redefine what forward_iterator is, even if I write this class outside of namespace std.
Question
I am fairly sure there should be a way to write an iterator class for the MFC CArray, which can be returned by begin and end functions. However, I am confused as to how to do this.
Further to the question of writing a working solution, I am beginning to wonder if there are any practical advantages to doing this? Does what I am trying to do even make sense? The raw pointer solution clearly works, so are there any advantages of investing the effort to write an iterator based solution? Can iterator solutions provide more sophisticated bounds checking, for example?
Since I managed to get this working I wanted to post a solution, hopefully I don't make too many errors transcribing it.
One thing that was not shown in the above code snippet is the fact that all these class and function definitions existed inside of namespace std. I posted another question about this earlier, and was informed that these things should not be inside namespace std. Correcting this seems to have resolved some problems and made the solution a step closer.
You can find that question here.
This is what I have so far: This is how to write an iterator for an external class which the programmer does not have access to. It also works for your own custom types or containers which you do have access to.
// How to write an STL iterator in C++
// this example is specific to the MFC CArray<T, U> type, but
// it can be modified to work for any type, note that the
// templates will need to be changed for other containers
#include <iterator>
#include <mfc stuff...>
template<typename T, typename U>
class CArrayForwardIt : public std::iterator<std::forward_iterator_tag, std::ptrdiff_t, U, U*, U&>
{
// the names used in this class are described in this list
// using iterator_category = std::forward_iterator_tag;
// using difference_type = std::ptrdiff_t;
// using value_type = U;
// using pointer = U*;
// using reference = U&;
public:
CArrayForwardIt(CArray<T, U> &array_ref, const std::size_t index)
: m_array_ref(array_ref)
, m_index(index)
{
}
// the only way I could get this to work was to make the return type
// an explicit U&, I don't know why this is required, as using
// reference operator*() const did not seem to work
U& operator*() const
{
if(m_index < m_array_ref.GetCount())
{
return m_array_ref[m_index];
}
else
{
throw std::out_of_range("Out of range Exception!");
}
}
CArrayForwardIt& operator++()
{
++ m_index;
return *this;
}
CArrayForwardIt operator++(int)
{
CForwardArrayIt tmp(*this);
++(*this);
}
friend bool operator==(const CArrayForwardIt& lhs, const CArrayForwardIt& rhs)
{
if(&(lhs.m_array_ref) == &(rhs.m_array_ref))
{
return lhs.m_index == rhs.m_index;
}
return false;
}
friend bool operator!=(const CArrayForwardIt& lhs, const CArrayForwardIt& rhs)
{
return !(lhs == rhs);
}
private:
std::size_t m_index;
CArray<T, U> &m_array_ref;
};
template<typename T, typename U>
auto begin(CArray<T, U> &array)
{
return CArrayForwardIt<T, U>(array, 0);
}
template<typename T, typename U>
auto end(CArray<T, U> &array)
{
return CArrayForwardIt<T, U>(array, array.GetCount());
}
int main()
{
CArray<int, int> array;
// do stuff to array
// construct vector from elements of array in one line
std::vector<int> vector(begin(array), end(array));
// also works with other STL algorithms
}
Note my comment about the U& operator* which produced some compiler error when written as reference operator* which might be a Visual Studio compiler bug. I'm not sure about this.
I would suggest that although this method is more difficult to implement (but not much when you know how to do it) it has the advantage of not using raw pointers which means that the iterator functions can provide proper exception throwing statements when illegal operations are attempted. For example, incrementing the iterator when it is already at the end.
Useful references:
https://lorenzotoso.wordpress.com/2016/01/13/defining-a-custom-iterator-in-c/
https://internalpointers.com/post/writing-custom-iterators-modern-cpp
For completeness, here is the simpler solution using raw pointers.
template<typename T, typename U>
auto begin(CArray<T, U> &array)
{
return &(array[0]);
}
template<typename T, typename U>
auto end(CArray<T, U> &array)
{
// get address of last element then increment
// pointer by 1 such that it points to a memory
// address beyond the last element. only works for
// storage containers where higher index elements
// are guaranteed to be at higher value memory
// addresses
if(array.GetCount() > 0)
{
return &(array[array.GetCount() - 1]) + 1;
}
else
{
return &(array[0]) + 1;
}
}
You can use these in the same way as demonstrated in the other answer, however there is also a way to use STL vector without the begin and end functions:
CArray<int, int> array; // ... do something to array
std::vector<int> vec(&array[0], &(array[array.GetCount() - 1]) + 1);
// note only works if elements guaranteed to be in continuous
// packed memory locations
but it also works with begin and end which is nicer
std::vector<int> vec(begin(array), end(array));
I am trying to make two types of objects compatible by using a container, to make it more difficult, my code must be able to calculate at compile time.
One of my objects type is structured as a functor in the following way:
template <std::size_t n,std::size_t p=1,typename number=double>
class Oper{
public:
constexpr Oper(const number d_=1.):d(d_){}
constexpr Oper(const Oper& oper_):d(oper_.d){}
const number d ;
constexpr auto operator()(const auto &v) const {
typename std::remove_const<typename std::remove_reference<decltype(v)>::type>::type w{} ;
/* Do some stuff to build w using v and d*/
return w ;}
};
The other object type is just std::array<std::array<double,n>,n>, i.e. matrices.
These objects are provided with the following operators as free functions:
template <std::size_t nr, std::size_t nc>
constexpr auto
operator *(const std::array<std::array<double,nc>,nr> & A,
const std::array<double,nc> & x){
std::array<double,nr> res{};
for (auto i=0u;i<nr;i++)
for (auto j=0u;j<nc;j++)
res[i] += A[i][j]*x[j];
return res;
}
template <std::size_t nrA, std::size_t ncA, std::size_t nrB, std::size_t ncB>
constexpr auto operator *(const std::array<std::array<double,ncA>,nrA> & A,
const std::array<std::array<double,ncB>,nrB> & B){
std::array<std::array<double,ncB>,nrA> res{};
for (auto k=0u;k<ncB;++k)
for (auto i=0u;i<nrA;++i)
for (auto j=0u;j<nrB;++j)
res[i][k] += A[i][j]*B[j][k];
return res ;
}
constexpr auto operator *(const auto & A,const auto & x){
return A(x) ;
}
Bear in mind I cannot change these implementations, they are provided to me.
To make these operators compatible I created the following classes
constexpr auto id = [](const auto v) {return v;};
template <typename Op, typename Preop=decltype(id)>
class Cont{
public:
constexpr Cont(const Op & op_,const Preop & preop_=id):op(op_),preop(preop_){}
constexpr Cont(const Cont & cont_):op(cont_.op),preop(cont_.preop){}
const Op op ;
const Preop preop ;
template <typename Op2, typename Preop2>
constexpr auto operator()(const Cont<Op2,Preop2> & mfop) const{
return Cont<const Cont<Op,Preop>,const Cont<Op2,Preop2>>
(Cont(op,preop),mfop);}
constexpr auto operator()(const auto & v) const{return op*(preop*v);}
};
Such that the following main compiles and works as expected
int main(){
const std::array<double,4> v{};
const Cont A(Oper<4,1>(1.));
const Cont B(std::array<std::array<double,4>,4>{std::array<double,4>{}});
constexpr auto res = B*B*A*A*B*A*B*v;
return 0;
}
Godbolt example here.
I am having issues with the Cont class
The idea of Cont when using functors only (no arrays), is to somehow place the code corresponding to different functors one after the other and, ideally, optimize the whole chain. Can that be achieved? How should I do it? Maybe some perfect forwarding magic?
There are many copies being done in Cont, this is causing a huge memory footprint of the compiler (GCC 8.2.0 with flags -std=c++1z -fconcepts -Ofast) when asking for a full compile time calculation. I believe this is because of all the copies being performed when putting the operators together. Could that be possible? Is there a way to avoid the copy?
Research
I found this old answer. I'd like to know if the solution is still effective or if there is a new, more effective way to do it.
Background
Lets suppose I have an iterator like below (specifics don't really matter, only that it is huge):
class inorder_iterator : public std::iterator<std::forward_iterator_tag, token>
{
friend syntax_tree;
node* current_node;
std::stack<node*> prev_nodes;
//std::stack<node*> visited_nodes;
std::map<node*, bool> visited;
public:
inorder_iterator();
inorder_iterator& operator++();
inorder_iterator operator++(int);
token& operator*();
const token& operator*() const;
token* operator->();
const token* operator->() const;
friend bool operator==(const inorder_iterator lhs, const inorder_iterator rhs);
friend bool operator!=(const inorder_iterator lhs, const inorder_iterator rhs);
private:
inorder_iterator(node* current);
node* find_leftmost_node(node* from);
};
Problem
Implementations of the member function declarations are of reasonable size, but I would like to reuse the current iterator to reduce code duplication.
Idea
First idea that came to mind is to templatize on node type, so I could pass const node to make it const iterator, but it just sounds fishy
template <typename Node>
//replace every occurrence of node with Node
// and use decltype(node.tk) instead of token everywhere
Also I'm not sure if this use of const is one of those "const belongs to implementation specifics" case.
A template is probably the only way to avoid code duplication. But I wouldn't leave the type parameter open. I'd simply use a boolean parameter that is fed to a std::conditional in order to determine the type:
template<bool IsConst> class iter_impl {
using value_type = std::conditional_t<IsConst, Node const, Node>;
};
The two iterator types of the container can then be either a couple of aliases, or if you want truly distinct types, a couple of classes that inherit from the template. Like so:
struct const_iterator : iter_impl<true> {};
struct iterator : iter_impl<false> {};
The benefit of using two new classes, is that you can define a converting constructor for const_iterator that allows it to be build from the non const iterator. This is akin to the standard library's behavior.
Also I'm not sure if this use of const is one of those "const belongs to implementation specifics" case.
The fact you use a const Node is indeed an implementation detail. But so long as it gives you the documented type behavior (an iterator to a const member of the container) I wouldn't stress too much over this.
You can safely define class template template< typename value_type > iterator parametrized by value type (const or non-const) with conversion operator to const version (i.e. operator interator< value_type const > () const):
template< typename type >
struct node
: node_base
{
union { type value; };
node() noexcept { ; }
node(node const &) = delete;
node(node &&) = delete;
void operator = (node const &) = delete;
void operator = (node &&) = delete;
~node() { ; }
type * pointer() noexcept { return &value; }
};
template< typename type >
struct tree_iterator
{
using value_type = type;
using reference = type &;
using pointer = type *;
using iterator_category = std::bidirectional_iterator_tag;
using difference_type = std::ptrdiff_t;
using node_type = node< value_type >;
using node_pointer = node_type *;
base_pointer p = nullptr;
pointer operator -> () const noexcept { return node_pointer(p)->pointer(); }
reference operator * () const noexcept { return *operator -> (); }
tree_iterator & operator ++ () noexcept { p = increment(p); return *this; }
tree_iterator operator ++ (int) noexcept { return {std::exchange(p, increment(p))}; }
tree_iterator & operator -- () noexcept { p = decrement(p); return *this; }
tree_iterator operator -- (int) noexcept { return {std::exchange(p, decrement(p))}; }
bool operator == (tree_iterator const & it) const noexcept { return p == it.p; }
bool operator != (tree_iterator const & it) const noexcept { return !operator == (it); }
operator tree_iterator< type const > () const { return {p}; }
};
In using const_iterator = iterator< value_type const >; the latter became no-op and can be called only intentionally (i.e. using syntax it.template operator iterator< value_type const > ()).
The code is given from here - rewrite of libstdc++'s red-black tree.
I have defined operator== as follows:
template <class State>
bool operator==(const std::shared_ptr<const State> &lhs,
const std::shared_ptr<const State> &rhs) {
return *lhs == *rhs;
}
This operator does not get instantiated (in gdb, I cannot set the break-point on the return statement -- the line does not exist).
However, this operator should be used by std::find called in this line:
return std::find(v.begin(), v.end(), el) != v.end();
I checked the type of v in the above line in gdb:
(gdb) whatis v
type = const std::vector<std::shared_ptr<Domains::IncWorst const>> &
(gdb) whatis el
type = const std::shared_ptr<Domains::IncWorst const> &
Doesn't this match my templated operator== with State being IncWorst?
I implemented a toy example as follows and the example works, so I cannot understand why the real code does not.
template<class V, typename T>
bool in(const V &v, const T &el) {
return std::find(v.begin(), v.end(), el) != v.end();
}
struct MyState {
MyState(int xx) : x(xx) {}
bool operator==(const MyState &rhs) const {
return x == rhs.x;
}
int x;
};
template <class State>
bool operator==(const std::shared_ptr<const State> &lhs,
const std::shared_ptr<const State> &rhs) {
return *lhs == *rhs;
}
int main() {
std::vector<std::shared_ptr<const MyState>> v{
std::make_shared<const MyState>(5)};
auto p = std::make_shared<const MyState>(5);
std::cout << in(v, p) << std::endl; // outputs 1
return 0;
}
Your operator== template is in the wrong namespace.
In order to be found by ADL, it must be either in the std namespace (which would be illegal, per [namespace.std]/1) or in Domains (per [basic.lookup.argdep]/2).
However, this is still highly dangerous, since if any template performing an equality comparison (e.g. but not limited to std::find) is instantiated both before and after your operator== template is declared, your whole program will be invalid per [temp.point]/8 and [basic.def.odr]/6.
If you must provide operator overload templates for std::shared_ptrs of your types, prefer to explicitly instantiate them after the declaration of each class, such that there is less chance of a template being instantiated somewhere the class is not visible:
struct MyState {
// ...
};
template bool operator==<MyState>(
const std::shared_ptr<MyState const>&,
const std::shared_ptr<MyState const>&);
This could still be problematic if someone forward-declares MyState somewhere else, but it's probably the best you can do.