Question

I'm trying to learn the currently accepted features of c++11 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.

Was it helpful?

Solution

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());

OTHER TIPS

You can't use auto in function arguments where you want the types of the arguments to be deduced. You use templates for that. Have a look at: http://thenewcpp.wordpress.com/2011/10/18/the-keyword-auto/ and http://thenewcpp.wordpress.com/2011/10/25/decltype-and-declval/. They both explain how to use auto and decltype. They should give you enough information about how they are used. In particular, another answer about make_instance could be done better with declval.

I think the point that Jarrah was making in his answer is that those points do explain exactly how to use these things. I will point out the details anyway:

You can't do this, there are two things wrong:

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

auto can't be used for function arguments. If you want the type of the function to be deduced then you should use templates. The second is that decltype takes an expression, whereas _Ty is a type. Here is how to solve it:

template <typename Ret>
auto
map(const function<Ret (_Ty)>& f)
  -> FList<decltype(f(declval<_Ty>()))>
{}

That way there is no magic to create an instance of a type.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top