I would like to understand why I get this operator redefinition error and why compiler tries to instantiate ft::Viterator<const int>
when I am only declaring a ft::vector<int>
in my main
In file included from mainconstit.cpp:3:
./vector.hpp:35:16: error: redefinition of 'operator=='
friend bool operator==(Viterator<T1> const &lhs, Viterator<T2> const &rhs) { return (lhs.ptr == rhs.ptr); }
^
./vector.hpp:208:23: note: in instantiation of template class 'ft::Viterator<const int>' requested here
for(iterator it = this->begin(); it != this->end(); it )
^
./vector.hpp:127:162: note: in instantiation of member function 'ft::vector<int, std::allocator<int> >::assign' requested here
vector(InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type()) : _alloc(alloc), _begin(NULL), _size(0), _capacity(0) { this->assign(first, last); }
^
mainconstit.cpp:10:14: note: in instantiation of function template specialization 'ft::vector<int, std::allocator<int> >::vector<int>' requested here
vector<int> v(4, 100);
^
./vector.hpp:35:16: note: previous definition is here
friend bool operator==(Viterator<T1> const &lhs, Viterator<T2> const &rhs) { return (lhs.ptr == rhs.ptr); }
^
with this code :
#ifndef VECTOR_HPP
# define VECTOR_HPP
/*...*/
namespace ft
{
template<class T>
class Viterator : public ft::iterator<std::random_access_iterator_tag, T>
{
public:
/*...*/
Viterator() : ptr(NULL) {}
Viterator(Viterator const & src) : ptr(src.ptr) {}
Viterator(pointer ptr) : ptr(ptr) {}
virtual ~Viterator() {}
operator Viterator<T const>() const { return (Viterator<T const>(this->ptr)); }
Viterator & operator=(Viterator const & rhs) {
this->ptr = rhs.ptr;
return (*this);
}
template<class T1, class T2>
friend bool operator==(Viterator<T1> const &lhs, Viterator<T2> const &rhs) { return (lhs.ptr == rhs.ptr); }
template<class T1, class T2>
friend bool operator!=(Viterator<T1> const &lhs, Viterator<T2> const &rhs) { return (lhs.ptr != rhs.ptr); }
/*...*/
private:
pointer ptr;
};
template<class T, class Alloc = std::allocator<T> >
class vector
{
public:
/*...*/
explicit vector(const allocator_type& alloc = allocator_type()) : _alloc(alloc), _begin(NULL), _size(0), _capacity(0) {}
explicit vector(size_type n, const value_type& val = value_type(), const allocator_type& alloc = allocator_type()) : _alloc(alloc), _begin(NULL), _size(0), _capacity(0) { this->assign(n, val); }
template<class InputIterator>
vector(InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type(), typename ft::enable_if<!ft::is_integral<InputIterator>::value>::type* = NULL) : _alloc(alloc), _begin(NULL), _size(0), _capacity(0) { this->assign(first, last); }
vector(const vector& x) : _begin(NULL), _size(0), _capacity(0) { *this = x; }
~vector() { this->_alloc.deallocate(this->_begin, this->_capacity); }
vector& operator=(const vector& x) {
this->assign(x.begin(), x.end());
return (*this);
}
iterator begin() { return (iterator(this->_begin)); }
const_iterator begin() const { return (const_iterator(this->_begin)); }
iterator end() { return (iterator(this->_begin this->_size)); }
const_iterator end() const { return (const_iterator(this->_begin this->_size)); }
/*...*/
template<class InputIterator>
void assign(InputIterator first, InputIterator last, typename ft::enable_if<!ft::is_integral<InputIterator>::value>::type* = NULL) {
this->resize(0);
this->resize(static_cast<size_type>(last - first));
InputIterator it2 = first;
for(iterator it1 = this->begin(); it2 != last; it1 )
{
this->_alloc.construct(&(*it1), *it2);
it2 ;
}
}
void assign(size_type n, const value_type& val) {
this->resize(0);
this->resize(n);
for(iterator it = this->begin(); it != this->end(); it )
this->_alloc.construct(&(*it), val);
}
/*...*/
private:
allocator_type _alloc;
pointer _begin;
size_type _size;
size_type _capacity;
};
}
#endif
CodePudding user response:
Defining a friend
function inside of a class places it into the enclosing namespace. If that class is a template, a new function is generated for every instantiation.
Instantiating Viterator
twice with different template arguments produces two copies of operator==
and operator!=
, with the exact same signatures, hence the error.
Possible solutions are:
- Define the operators outside of the class. If needed, you can declare them as
friend
s (as opposed to define). - Make the operators non-template, with
Viterator<T>
parameters (causing the operators produced by different instantiations to have different signatures, removing the error). This stops you from comparing iterators with different template arguments, which may or may not be good.
CodePudding user response:
You can shorten your example significantly to still get the point across:
template <typename T>
class Foo {
T * ptr = nullptr;
// problem: defined in each Foo instantiation
template <typename X, typename Y>
bool friend operator==(Foo<X> const& left, Foo<Y> const& right) {
return left.ptr == right.ptr;
}
};
int main() {
Foo<int> fint;
Foo<char> fchar;
}
The problem is that the friend function is itself a template and it isn't tied to the containing class, but is defined in the containing class. Yet friend functions are not actually class members, but are free-standing functions. So we get multiple competing definitions in the same namespace when 2 or more Foo<>
types are instantiated.
If you move the definition outside of the class, then it's only defined once:
template <typename T>
class Foo {
T* ptr;
template <typename X, typename Y>
bool friend operator==(Foo<X> const& left, Foo<Y> const& right);
};
template <typename X, typename Y>
bool operator==(Foo<X> const& left, Foo<Y> const& right) {
return left.ptr == right.ptr;
}
And the different Foo instantiations merely declare it as a friend, but do not define it.
But stepping back, why do this? Do you really need to compare Foo<int>
with Foo<char>
? The pointer comparison won't compile anyway, due to mismatching types. The only case I can think of for 2 template parameters for this comparison is if T and U were base/derived class types, but even then it seems kind of nonsensical.
So remove the template from the function, and then you could also do this:
template <typename T>
class Foo {
T* ptr;
bool friend operator==(Foo<T> const& left, Foo<T> const& right) {
return left.ptr == right.ptr;
}
};
Notice, now the friend is definining only a single type of comparison, between two Foo<T>
, and the parameter is decided by the containing class. So now two instantiations of foo will produce overloads of operator==, not competing identical definitions.
I also would mention that in c 20 you can avoid the boilerplate definition, and have the compiler generate it for you with = default
in place of the function body.