问题
I'm creating a custom iterator and I'm having trouble satisfying the scenario where I create a const
iterator and initialize it with a non-const
begin()
. This is legal according to the STL and can be demonstrated with std::string:
#include <string>
using namespace std;
int main() {
string::iterator a;
string::const_iterator b = a;
return 0;
}
I can't figure out how to make it work:
template<typename T>
class some_class {
};
int main() {
some_class<int> a;
// Works OK!
const some_class<int> const_b = a;
// error: conversion from 'some_class<int>' to non-scalar type 'const some_class<const int>'
const some_class<const int> const_c = a;
return 0;
}
UPDATE
@ALEXANDER KONSTANTINOV provided a solution but it does not satisfy all possible STL test cases. I'm testing @Bo Persson's suggestion next. Changing the constructor to const some_class<U>& other
will allow it to compile but then iterator a = const_iterator b
would also erroneously be true.
#include <string>
using namespace std;
template<typename T>
class some_class {
public:
some_class() {
}
template<typename U>
some_class(some_class<U>& other) {
}
};
namespace name {
typedef some_class<int> iterator;
typedef const some_class<const int> const_iterator;
}
int main() {
string::iterator a;
string::const_iterator b = a;
const string::iterator c;
string::iterator d = c;
string::const_iterator e = c;
name::iterator f;
name::const_iterator g = f;
const name::iterator h;
name::iterator i = h;
name::const_iterator j = h; // <- Error
return 0;
}
UPDATE
There seems to be some confusion regarding adding const
to the constructor. Here is a test case:
// This is not allowed by the STL
//string::const_iterator _a;
//string::iterator _b = _a; // <- Error!
// This should NOT compile!
name::const_iterator _a;
name::iterator _b = _a;
回答1:
First of all - you cannot assume that std::string::const_iterator
is just "const version" of regular iterator - like this const std::string::iterator
.
When you look at your STL library implementation (this is just example from gcc4.9.2 STL header for basic_string):
typedef __gnu_cxx::__normal_iterator<pointer, basic_string> iterator;
typedef __gnu_cxx::__normal_iterator<const_pointer, basic_string>
const_iterator;
As you can see - what differs both iterators is the return pointer value - pointer
vs const_pointer
- and that is the case - "const iterator" is not something that cannot change - but something that returns const pointer/references so you cannot modify the values the iterator iterates over.
So - we can investigate further and see how the desired copying from non const to const version was achieved:
// Allow iterator to const_iterator conversion
template<typename _Iter>
__normal_iterator(const __normal_iterator<_Iter,
typename __enable_if<
(std::__are_same<_Iter, typename _Container::pointer>::__value),
_Container>::__type>& __i) _GLIBCXX_NOEXCEPT
: _M_current(__i.base()) { }
So, basically - this constructor accepts any instance of the same template (__normal_iterator
) - but it has enable_if
closure to allow only instance of const pointer.
I believe you shall do the same in your case
- Have real const_iterator - not just const version of regular iterator
- And have template constructor from const_iterator with enable_if restriction to disallow constructing from anything (I mean iterator over ints from iterator over std::strings)
As per your example:
#include <type_traits>
template<typename T>
class some_class {
public:
some_class() {
}
template <typename U>
using allowed_conversion_from_non_const_version = std::enable_if_t<std::is_same<std::remove_cv_t<T>,U>::value>;
template<typename U, typename EnableIf = allowed_conversion_from_non_const_version<U>>
some_class(const some_class<U>&) {
}
template<typename U, typename EnableIf = allowed_conversion_from_non_const_version<U>>
some_class& operator = (const some_class<U>&) {
}
};
Two things to read from this example:
- Assignment operator is also needed
- You shall enable only from non-const to const version - and this is achieved by combination of
enable_if
/remove_cv
(remove_const
also works - but why not construct also volatile version - anywaycv
is shorter thanconst
)
回答2:
You should define a template copy constructor for your class
template<typename T>
class some_class {
public:
template<typename U> friend class some_class;
some_class()
{
}
template<typename U>
some_class(const some_class<U> &other)
: data(other.data)
{}
private:
T* data;
};
int main() {
some_class<int> a;
// Works OK!
const some_class<int> const_b = a;
// error: conversion from 'some_class<int>' to non-scalar type 'const some_class<const int>'
const some_class<const int> const_c = a;
return 0;
}
回答3:
Copy constructor should have a const reference and this code will compile
some_class(const some_class<U>& other){}
来源:https://stackoverflow.com/questions/35309197/how-to-define-a-copy-constructor-for-a-const-template-parameter