Reputation: 29473
Note: I have written a "recipe" based on the lessons learnt from the exercise and the answers & comments on this page, see http://www.codeproject.com/Tips/1029941/Python-like-enumeration-in-Cplusplus.
I'm playing around with the extensions that C++11 brings to C++03. I want to be able to iterate over a container using the following code:
int main()
{
std::list<int> list = { 1, 2, 3, 4, 5, 6, 7 };
for (auto x : enumerated(list))
cout << x.first << " " << x.second << endl;
for (auto x : const_enumerated(list))
cout << x.first << " " << x.second << endl;
}
The first iteration has x modifiable, vs for the second, attempting to modify x would lead to a compile error. I have something that works for the non-const case:
template <typename Container>
class EnumerationAdaptor
{
public:
EnumerationAdaptor(Container& container) : container_(container) {}
EnumIter<typename Container::iterator> begin() const { return container_.begin(); }
EnumIter<typename Container::iterator> end() const { return container_.end(); }
private:
Container& container_;
};
template <typename Container>
EnumerationAdaptor<Container> enumerated(Container& container) { return container; }
template <typename Container>
EnumerationAdaptor<const Container> const_enumerated(const Container& container) { return container; }
The non-const case uses EnumIter<std::list<...>::iterator>
, as desired, and I'm trying to make the const case use EnumIter<std::list<...>::const_iterator>
as return type of begin()
and end()
. Seems like I need decltype:
template <typename Container>
class EnumerationAdaptor
{
public:
EnumerationAdaptor(Container& container) : container_(container) {}
EnumIter<decltype(Container().begin())> begin() const { return container_.begin(); }
EnumIter<decltype(Container().end())> end() const { return container_.end(); } // *** compile error (see below)
private:
Container& container_;
};
But I get a compilation error in Visual Studio 2015 Express:
Error C2440 'return': cannot convert from
'std::_List_const_iterator<std::_List_val<std::_List_simple_types<int>>>'
to
'EnumIter<std::_List_iterator<std::_List_val<std::_List_simple_types<int>>>>'
[in c:\users\...\enumeratedcpp.cpp line 46, which is line marked ***]
which suggests that I'm doing something wrong with decltype, as the compiler is finding the non-const begin(). Is there a way to fix this?
EDIT: even with a simple EnumIter, problem is same:
template <typename Iter>
class EnumIter
{
public:
EnumIter(Iter begin) : iter_(begin) {}
EnumIter& operator++()
{
return *this;
}
bool operator!=(const EnumIter& rhs)
{
return iter_ != rhs.iter_; // or self.index_ != rhs.index_;
}
int operator*() const
{
return index_;
}
private:
Iter iter_;
int index_ = 0;
};
Upvotes: 2
Views: 1019
Reputation: 302748
There is one issue with this expression:
decltype(Container().begin())
which is that Container()
only works if Container
happens to be default-constructible. That limits the usability of your class for no reason. (There is a lesser issue which is that this won't work for raw arrays, but that's another exercise).
Besides that, the code is perfectly valid for class types. From [expr.type.conv]:
The expression T(), where T is a simple-type-specifier or typename-specifier for a non-array complete object type or the (possibly cv-qualified) void type, creates a prvalue of the specified type [...]
So if Container
is const list<int>
, then the type of that whole expression should be list<int>::const_iterator
. If MSVC gives you something else, that's a bug.
That said, we really should address the default-constructibility issue. That's where std::declval
comes in:
decltype(std::declval<Container&>().begin())
This will not impose any restrictions on Container
, and perhaps MSVC will handle this correctly.
Upvotes: 2