views:

113

answers:

3

I'm a Scala/Java programmer looking to reintroduce myself to C++ and learn some of the exciting features in C++0x. I wanted to start by designing my own slightly functional collections library, based on Scala's collections, so that I could get a solid understanding of templates. The problem I'm running into is that the compiler doesn't seem to be able to infer any type information for templated function objects.

FC++ seems to have solved this using "Signatures". These seem really similar to the result_type typename, and I thought I would get this using the new function syntax. Can anyone suggest a way to do this sort of thing in C++0x, if it's possible, or at least explain how FC++ was able to accomplish this? Here's a snippet of code I was playing around with

#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

template<class T>
class ArrayBuffer {
private:
    vector<T> array;
public:
    ArrayBuffer();
    ArrayBuffer(vector<T> a) : array(a) {}

    template<typename Fn>
    void foreach(Fn fn) {
        for(unsigned int i = 0; i < array.size(); i++) fn(array[i]);
    }

    template<typename Fn>
    auto map(Fn fn) -> ArrayBuffer<decltype(fn(T()))> {
        vector<decltype(fn(T()))> result(array.size());
        for(int unsigned i = 0; i < array.size(); i++) result[i] = fn(array[i]);
        return result;
    }
};

template<typename T>
class Print {
    public:
    void operator()(T elem) { cout<<elem<<endl; }
};

template<typename T>
class Square{
public:
    auto operator()(T elem) -> T {
        return elem * elem;
    }
};

int main() {
    vector<int> some_list = {5, 3, 1, 2, 4};
    ArrayBuffer<int> iterable(some_list);
    ArrayBuffer<int> squared = iterable.map(Square<int>()); // works as expected
    iterable.foreach(Print<int>()); // Prints 25 9 1 4 16 as expected
    iterable.foreach(Print()); // Is there a way or syntax for the compiler to infer that the template must be an int?
    ArrayBuffer<int> squared2 = iterable.map(Square()); // Same as above - compiler should be able to infer the template.
}
+5  A: 

You can make the operator() a template too

class Print {
    public:
    template<typename T>
    void operator()(T elem) { cout<<elem<<endl; }
};

Then you can pass Print(). For passing arguments like in ArrayBuffer<decltype(fn(T()))> I recommend using declval, so you could also work with non-default constructible T

ArrayBuffer<decltype(fn(declval<T>()))>
Johannes Schaub - litb
Thank you! This works perfectly. I didn't think to move the template to the operator. I appreciate the tip about declval as well. I do have a brief follow up - is there a way to do this using a templated function pointer? For instance, if we have a template print function, I could call iterable.foreach( I believe the answer is no.
Joshua Hartman
A: 

Is there a way or syntax for the compiler to infer that the template must be an int?

The problem is the template doesn't need to be an int.
This is equally valid.

iterable.foreach(Print<float>());

What you're really asking is.
Can i make a template functor have a different default depending on the context in which it is used?

The answer is no. I suppose we could theoretically do it for simple cases. However the corner cases quickly make this infeasible. The true context of the function is the complete compilation unit, not just the line where it is instantiated.

caspin
+1  A: 

You seem to be reinventing the C++ standard library. I'm not talking about your containers.
C++ is already pretty functionally equipped.

I think you're missing a few key points about the C++ standard library.

  • It is Generic first and Object Oriented second.
    If an algrithm can be implemented in a generic way it will not be included with the class.
    ArrayBuffer::foreach == std::for_each
    ArrayBuffer::map == std::transform
  • The standard algorithms work on iterators rather than complete containers. This is often missed by new C++ programmers because both Java and C# lack the concept. Iterators are more expressive/flexible than containers alone. Iterators are arguably the way to go. That said, Ranges are much more terse way to express iterators (a range is just paired iterators).

Here's an example of using C++ functionally. It's also a good example of why C# didn't use iterators. Though they are very powerful, their verbosity is intimidating to C#'s target audience. Java doesn't use iterators as they aren't Object Oriented and the language designers were really anal about that in the beginning.

struct Print
{
   template<typename T>
   void operator()( const T& t )
   { std::cout << t << std::endl; }
};

struct Squared
{
   template<typename T>
   T operator()( const T& t )
   { return t*t; }
};

int main()
{
   std::vector<int> vi;
   std::foreach( vi.begin(), vi.end(), Print());
   std::foreach( vi.begin(), vi.end(), [](int i){ std::cout<<i<<std::endl; } );

   std::vector<int> vi_squared;

   std::transform( vi.begin(), vi.end(), std::back_inserter(vi_squared), Squared() );
   // or
   vi_squared.resize( vi.size() );
   std::transform( vi.begin(), vi.end(), vi_squared.begin(), Squared() );
}
caspin
Thanks for the tips. I understand how the STL separates some of the algorithms from the containers in a nice way. It still leaves many things out like folds, reduces, views, slices, flattens, flatMaps, etc. Finally, transform in C++ is destructive. I want to implement fully persistent collections in addition to mutable collections, and this can't be done with the split out design of the STL. A map should be an operation on the container, and the container should be able to choose how to implement it.
Joshua Hartman
Actually, I take that back. Transform doesn't seem to be destructive. Still, I think having the operations as part of the containers makes sense and lets each container implement its own specific behavior. I'm not sure how much code reuse I'll be able to get out of this design though as my algorithms will be mixed in with the iteration. I'm also hoping to use CRTP to unite any different collections I implement under one hierarchy so that I can present an interface, rather than an implementation from a library (template methods can't be virtual). Ranges look awesome - I appreciate the link.
Joshua Hartman
You can still specialize a containers behavior when it's used on `std::transform` or any std algorithm. Instead of subclassing and overriding the map method, you specialized (technically partial-specialization) the `std::transform` method for your container. Usually, defining how your collection is traversed is enough and iterators do that really well (and ranges/views do it better). This feels right to me. 99% of the time a map algorithm doesn't care about the storage semantics. But if you really need to get in there and do crazy things you can specialize.
caspin