Oliver
Oliver

Reputation: 29473

template parameter for const iterator instead of iterator

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

Answers (1)

Barry
Barry

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

Related Questions