views:

598

answers:

1

I'm trying to learn the currently accepted features of c++0x and I'm having trouble with auto and decltype. As a learning exercise I'm extending the std class list with some generic functions.

template<class _Ty, class _Ax = allocator<_Ty>>
class FList : public std::list<_Ty, _Ax>
{
public:
    void iter(const function<void (_Ty)>& f)
    {
        for_each(begin(), end(), f);
    }

    auto map(const function<float (_Ty)>& f) -> FList<float>*
    {
        auto temp = new FList<float>();

        for (auto i = begin(); i != end(); i++)
            temp->push_back(f(*i));

        return temp;
    }
};

auto *ints = new FList<int>();
ints->push_back(2);
ints->iter([](int i) { cout << i; });

auto *floats = ints->map([](int i) { return (float)i; });
floats->iter([](float i) { cout << i; });

For the member map I want the return type to be generic depending on what the passed function returns. So for the return type I could do something like this.

auto map(const function<float (_Ty)>& f) -> FList<decltype(f(_Ty))>*

This would also need to remove the float type in the function template.

auto map(const function<auto (_Ty)>& f) -> FList<decltype(f(_Ty))>*

I could use a template class but that makes the use of instances more verbose since i have to specify the return type.

template<class T> FList<T>* map(const function<T (_Ty)>& f)

To sum of my question i'm trying to figure out how to define map without using a template class and still have it generic in the type it returns.

+9  A: 

Deriving from std::list or other std:: containers is discouraged.

Write your operations as free functions so they can work on any standard container via iterators.

Do you mean "define map without using a template function"?

You should be able to use the result_type member type of std::function to get the type it returns.

Also it's not necessary for you to specify that the function is passed as a std::function. You could leave it open as any type, and let the compiler join everything up. You only need std::function for runtime polymorphism.

And using new to create raw heap-allocation objects and returning them by pointer is soooo 1992! :)

Your iter function is essentially the same thing as the range-based for loop.

But all that aside... do you mean something like this?

template <class TFunc>
auto map(const TFunc &f) -> FList<decltype(f(_Ty()))>*
{
    auto temp = new FList<decltype(f(_Ty()))>();

    for (auto i = begin(); i != end(); i++)
        temp->push_back(f(*i));

    return temp;
}

This will match anything callable, and will figure out the return type of the function by using decltype.

Note that it requires _Ty to be default constructable. You can get around that by manufacturing an instance:

template <class T>
T make_instance();

No implementation is required because no code is generated that calls it, so the linker has nothing to complain about (thanks to dribeas for pointing this out!)

So the code now becomes:

FList<decltype(f(make_instance<_Ty>()))>*

Or, literally, a list of whatever the type would be you'd get from calling the function f with a reference to an instance of _Ty.

And as a free bonus for accepting, look up rvalue references - these will mean that you can write:

std::list<C> make_list_somehow()
{
    std::list<C> result;
    // blah...
    return result;
}

And then call it like this:

std::list<C> l(make_list_somehow());

Because std::list will have a "move constructor" (like a copy constructor but chosen when the argument is a temporary, like here), it can steal the contents of the return value, i.e. do the same as an optimal swap. So there's no copying of the whole list. (This is why C++0x will make naively-written existing code run faster - many popular but ugly performance tricks will become obsolete).

And you can get the same kind of thing for free for ANY existing class of your own, without having to write a correct move constructor, by using unique_ptr.

std::unique_ptr<MyThing> myThing(make_my_thing_somehow());
Daniel Earwicker
i'm trying to learn how to use auto and decltype not asking about best practices.
gradbot
Yeah, but there's a load of other stuff you'd probably benefit from knowing as well. I was on a train journey before so I got interrupted before I got to the decltype stuff. Updated it now.
Daniel Earwicker
Awesome thanks!
gradbot
IIRC, you do not need to define the implementation, but only declare it for decltype() to work, that is, I believe (I cannot test with any C++0x compiler now) that 'template <typename T> T fake_type();' suffices to write 'decltype(fake_type<T>())'
David Rodríguez - dribeas
@dribeas - excellent! Works on VC++ 2010 Beta 1. Will update.
Daniel Earwicker
as an added bonus, don't use identifiers starting with underscore + capital letter. Those are reserved to the implementation. So std::list is allowed to use them, your derived class is not. :)
jalf
I thought it was anything starting with an underscore that was reserved for the implementation?
Daniel Earwicker
I have just confirmed the same behavior (which is also the expected behavior according to the current draft) with g++ 4.4.
David Rodríguez - dribeas
Thanks guys. I'm going to drop std::list ;) and work in my own namespace. c++0x is looking to be some exciting stuff, I just wish the compilers would add the new features faster.
gradbot
Might be in for a long wait for some things. I'd guess that concepts will be done last in most commercial compilers, simply because they are a ton of new complexity, and in return we get... clearer error messages. Important, yes, but in terms of return on investment, it's nowhere.
Daniel Earwicker
Hah, the trick with `make_instance` is clever. Just found your answer looking for an answer to basically the same problem. :) I'm surprised I didn't upvote this answer back when I first commented on it. +1 now.
jalf
I learned it from the boost mpl source - there were several versions of it scattered around in there at the time (around 2005 I think).
Daniel Earwicker