Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++0x template function object inference

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.
}
like image 778
Joshua Hartman Avatar asked Sep 28 '10 05:09

Joshua Hartman


2 Answers

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>()))>
like image 165
Johannes Schaub - litb Avatar answered Oct 24 '22 00:10

Johannes Schaub - litb


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() );
}
like image 29
deft_code Avatar answered Oct 24 '22 02:10

deft_code