5

Soy un programador de Scala/Java que busca reintroducirse en C++ y aprender algunas de las características interesantes en C++ 0x. Quería comenzar diseñando mi propia biblioteca de colecciones ligeramente funcional, basada en las colecciones de Scala, para poder obtener una comprensión sólida de las plantillas. El problema al que me estoy enfrentando es que el compilador no parece ser capaz de inferir ningún tipo de información para los objetos de función con plantilla.C++ 0x función objeto de función inferencia

FC++ parece haber resuelto esto usando "Firmas". Estos parecen muy similares al tipo de nombre result_type, y pensé que obtendría esto usando la nueva sintaxis de la función. ¿Alguien puede sugerir una manera de hacer este tipo de cosas en C++ 0x, si es posible, o al menos explicar cómo FC++ fue capaz de lograr esto? He aquí un fragmento de código que estaba jugando con

#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. 
} 

Respuesta

5

Puede hacer que el operator() una plantilla demasiado

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

A continuación, puede pasar Print(). Para pasar argumentos al igual que en ArrayBuffer<decltype(fn(T()))> recomiendo el uso de declval, por lo que también podría trabajar con no predeterminado construible T

ArrayBuffer<decltype(fn(declval<T>()))> 
+0

¡Gracias! Esto funciona perfectamente No pensé mover la plantilla al operador. Aprecio el consejo sobre declval también. Tengo un seguimiento breve: ¿hay alguna manera de hacerlo utilizando un puntero de función con plantillas? Por ejemplo, si tenemos una función de impresión de plantilla, podría llamar a iterable.foreach (& print ); Creo que la respuesta es no. –

0

¿Hay una manera o de sintaxis para el compilador para inferir que la plantilla debe ser un int ?

El problema es la plantilla no necesita ser un int.
Esto es igualmente válido.

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

Lo que realmente está preguntando es.
¿Puedo hacer que un funtor de plantilla tenga un valor predeterminado diferente según el contexto en el que se utiliza?

La respuesta es no. Supongo que teóricamente podríamos hacerlo por casos simples. Sin embargo, los casos de esquina rápidamente lo hacen inviable. El verdadero contexto de la función es la unidad de compilación completa, no solo la línea donde se crea una instancia.

1

Parece que está reinventando la biblioteca estándar de C++. No estoy hablando de tus contenedores.
C++ ya está bastante funcionalmente equipado.

Creo que te faltan algunos puntos clave sobre la biblioteca estándar de C++.

  • Es Genérico primera y orientada a objetos segundos.
    Si se puede implementar un algoritmo de forma genérica, no se incluirá con la clase.
    ArrayBuffer::foreach == std::for_each
    ArrayBuffer::map == std::transform
  • Los algoritmos estándar funcionan en iteradores en lugar de contenedores completos. Esto a menudo se pierde por los nuevos programadores de C++ porque tanto Java como C# carecen del concepto. Los iteradores son más expresivos/flexibles que los contenedores solos. Iteradores son arguably el camino a seguir.Dicho esto, Ranges son una forma mucho más concisa de expresar iteradores (un rango son solo iteradores emparejados).

Aquí hay un ejemplo del uso funcional de C++. También es un buen ejemplo de por qué C# no usó iteradores. Aunque son muy poderosos, su verbosidad es intimidante para el público objetivo de C#. Java no usa iteradores ya que no están Orientado a objetos y los diseñadores de lengua fueron realmente anal sobre eso al principio.

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()); 
} 
+0

Gracias por los consejos. Entiendo cómo el STL separa algunos de los algoritmos de los contenedores de una manera agradable. Todavía deja muchas cosas como pliegues, reduce, vistas, rebanadas, aplana, mapas planos, etc. Finalmente, transformar en C++ es destructivo. Quiero implementar colecciones totalmente persistentes además de colecciones mutables, y esto no se puede hacer con el diseño dividido de STL. Un mapa debe ser una operación en el contenedor, y el contenedor debe poder elegir cómo implementarlo. –

+0

En realidad, retiro eso. La transformación no parece ser destructiva. Aún así, creo que tener las operaciones como parte de los contenedores tiene sentido y permite que cada contenedor implemente su propio comportamiento específico. No estoy seguro de cuánta reutilización de código voy a poder sacar de este diseño, ya que mis algoritmos se mezclarán con la iteración. También espero usar CRTP para unir cualquier colección diferente que implemente bajo una jerarquía para poder presentar una interfaz, en lugar de una implementación de una biblioteca (los métodos de la plantilla no pueden ser virtuales). Los rangos se ven increíbles. Aprecio el enlace. –

+0

Aún puede especializar el comportamiento de un contenedor cuando se usa en 'std :: transform' o cualquier algoritmo estándar. En lugar de subclasificar y anular el método del mapa, se especializa (técnicamente de especialización parcial) el método 'std :: transform' para su contenedor. Por lo general, basta con definir cómo se atraviesa su colección y los iteradores lo hacen realmente bien (y los rangos/vistas lo hacen mejor). Esto se siente bien para mí. El 99% del tiempo, un algoritmo de mapa no se preocupa por la semántica de almacenamiento. Pero si realmente necesitas entrar y hacer cosas locas, puedes especializarte. –