2010-04-11 14 views
7

En lenguajes orientados numéricos (Matlab, Fortran), la operación y semántica del rango es muy útil cuando se trabaja con datos multidimensionales. Por ejemplo:Operadores de lenguaje incrustado de dominio C++

A(i:j,k,:n) // represents two-dimensional slice B(i:j,0:n) of A at index k 

desgracia C++ no tiene operador de rango (:). por supuesto, se puede emular usando un functor rango/división, pero la semántica es menos limpia que Matlab. Estoy creando un prototipo de dominio de matriz/tensor en C++ y me pregunto si hay alguna opción para reproducir el operador de rango. Todavía me gustaría confiar exclusivamente en el framework C++/prprocessor.

Hasta ahora he visto a través de la onda de refuerzo que podría ser una opción adecuada.

¿hay algún otro medio para introducir nuevos operadores no nativos a C++ DSL?

Sé que no puede agregar nuevos operadores. Estoy buscando específicamente una solución alternativa. Una cosa que se me ocurrió (corte muy feo y no tengo la intención de usar):

#define A(r) A[range(((1)?r), ((0)?r))] // assume A overloads [] 
A(i:j); // abuse ternary operator 

Respuesta

3

Una solución que he utilizado antes es escribir un preprocesador externo que analiza la fuente y reemplaza cualquier uso de su operador personalizado con C++. Para sus propósitos, los usos de a : b serían reemplazados con algo así como a.operator_range_(b), y operator:() declaraciones con declaraciones de range_ operator_range_(). En su archivo MAKE luego agrega una regla que preprocesa los archivos fuente antes de compilarlos. Esto se puede hacer con relativa facilidad en Perl.

Sin embargo, después de haber trabajado con una solución similar en el pasado, no lo recomiendo. Tiene el potencial de crear problemas de mantenimiento y portabilidad si no permanece atento a cómo se procesa y genera la fuente.

+0

eso es una idea. En principio, puedo reemplazar ':' con algún otro operador binario global que genere rango y use ',' para anexar rango. Estoy al tanto de posibles problemas, solo quiero probarlo, algún tipo de herramienta de desarrollo rápido de aplicaciones – Anycorn

+0

Buena suerte. Tenga cuidado de no chocar con '?:' Y las etiquetas de los especificadores de acceso, los casos de cambio y (si los usa) gotos. De hecho, recomendaría usar algo como '..' porque sería más fácil de analizar. –

2

Como dijo Billy, no puede sobrecargar a los operadores. Sin embargo, puede acercarse mucho a lo que desea con sobrecarga de operador "regular" (y tal vez con metaprogramación de plantillas). Sería muy fácil para permitir algo como esto:

#include <iostream> 

class FakeNumber { 
    int n; 
public: 
    FakeNumber(int nn) : n(nn) {} 
    operator int() const { return n; } 
}; 

class Range { 
    int f, t; 
public: 
    Range(const int& ff, const int& tt) : f(ff), t(tt) {}; 
    int from() const { return f; } 
    int to() const { return t; } 
}; 

Range operator-(const FakeNumber& a, const int b) { 
    return Range(a,b); 
} 

class Matrix { 
public: 
    void operator()(const Range& a, const Range& b) { 
     std::cout << "(" << a.from() << ":" << a.to() << "," << b.from() << ":" << b.to() << ")" << std::endl; 
    } 
}; 

int main() { 
    FakeNumber a=1,b=2,c=3,d=4; 
    Matrix m; 
    m(a-b,c-d); 

    return 0; 
} 

La desventaja es que esta solución no es compatible con las expresiones de todos los literales. Ya sea desde o para tener que ser clases definidas por el usuario, ya que no podemos sobrecargar el operador para dos tipos primitivos.

También puede sobrecargar operator* para permitir la especificación de paso a paso, así:

m(a-b*3,c-d); // equivalent to m[a:b:3,c:d] 

y la sobrecarga de las dos versiones de operator-- para permitir ignorar uno de los límites:

m(a--,--d); // equivalent to m[a:,:d] 

Otra opción es definir dos objetos, llamados algo como Matrix :: start y Matrix :: end, o lo que quieras, y luego en lugar de usar operator--, puedes usarlos, y luego el otro límite no tendría que ser una variable y podría ser un literal:

m(start-15,38-end); // This clutters the syntax however 

Y, por supuesto, puede usar las dos formas.

Creo que es lo mejor que puedes obtener sin recurrir a soluciones extrañas, como herramientas de preconstrucción personalizadas o maltrato macro (del tipo presentado por Matthieu y sugerido en contra de usarlas :)).

+0

¡Cualquiera que sobrecargue el operador * para ser utilizado en un CONTEXTO NUMÉRICO para especificar un significado diferente va a ser un "poster engañoso del siglo" en nuestra oficina! ¡Uf! –

1

La solución más fácil es usar un método en matriz en lugar de un operador.

A.range(i, j, k, n); 

Tenga en cuenta que normalmente no se utiliza , en un operador subíndice [], por ejemplo, en lugar de A[i][j]A[i,j]. La segunda forma podría ser posible al sobrecargar al operador de coma, pero luego forzará i y j para que sean objetos, no números.

Puede definir una clase range que podría usarse como un subíndice para su clase de matriz.

class RealMatrix 
{ 
public: 
    MatrixRowRangeProxy operator[] (int i) { 
     return operator[](range(i, 1)); 
    } 

    MatrixRowRangeProxy operator[] (range r); 

    // ... 

    RealMatrix(const MatrixRangeProxy proxy); 
}; 

// A generic view on a matrix 
class MatrixProxy 
{ 
protected: 
    RealMatrix * matrix; 
}; 


// A view on a matrix of a range of rows 
class MatrixRowRangeProxy : public MatrixProxy 
{ 
public: 
    MatrixColRangeProxy operator[] (int i) { 
     return operator[](range(i, 1)); 
    } 

    MatrixColRangeProxy operator[] (const range & r); 

    // ... 
}; 

// A view on a matrix of a range of columns 
class MatrixColRangeProxy : public MatrixProxy 
{ 
public: 
    MatrixRangeProxy operator[] (int i) { 
     return operator[](range(i, 1)); 
    } 

    MatrixRangeProxy operator[] (const range & r); 

    // ... 
}; 

Luego puede copiar un rango de una matriz a otra.

RealMatrix A = ... 
RealMatrix B = A[range(i,j)][range(k,n)]; 

último mediante la creación de una clase Matrix que puede contener o bien un RealMatrix o una MatrixProxy se puede hacer una RealMatrix y una MatrixProxy parece el mismo desde el exterior.

Tenga en cuenta que operator[] en los proxies no son ni pueden ser virtuales.

0

Si quiere divertirse, puede consultar IdOp.

Si realmente está trabajando en un proyecto, no sugiero usar este truco sin embargo. El mantenimiento sufrirá de astutos trucos.

Su mejor apuesta es, por tanto, morder la bala y usar notación explícita. Una función corta llamada range que produce un objeto definido personalizado para el cual los operadores están sobrecargados parece especialmente adecuada.

Matrix<10,30,50> matrix = /**/; 
MatrixView<5,6,7> view = matrix[range(0,5)][range(0,6)][range(0,7)]; 
Matrix<5,6,7> n = view; 

Nota que el operator[] sólo tiene 4 sobrecargas (const/no const + int/gama básica) y los rendimientos de un objeto proxy (hasta la última dimensión). Una vez aplicado a la última dimensión, da una vista de la matriz. Se puede construir una matriz normal desde una vista que tenga las mismas dimensiones (constructor no explícito).

+0

En lugar de jugar con 'operator []()', prefiero definir una función de miembro 'Matrix' que tome una subvista. La sobrecarga de las funciones del operador puede causar problemas inesperados. –

+0

Hum, sí, incluso mejor: p ¡Ahorraría en el tipeo y también en la legibilidad! –

2

Una alternativa es construir un dialecto de variantes C++ utilizando una herramienta de transformación de programas.

El DMS Software Reengineering Toolkit es un motor de transformación de programa, con una resistencia industrial C++ Front End. DMS, al usar este front end, puede analizar C++ completo (incluso tiene un preprocesador y puede retener la mayoría de las directivas de preprocesador sin expandir), crea automáticamente AST y completa tablas de símbolos.

La parte frontal de C++ viene en origen, utilizando una gramática derivada directamente del estándar. Es técnicamente sencillo agregar nuevas reglas gramaticales, incluidas aquellas que permitirían la sintaxis ":" como subíndices de matriz como ha descrito, y como Fortran90 + ha implementado. Entonces, uno puede usar la capacidad de transformación de programas de DMS para transformar la sintaxis "nueva" en C++ "vainilla" para su uso en compiladores convencionales de C++. (Este esquema es una generalización del modelo de Programación Intencional de "agregar conceptos de DSL a su idioma").

De hecho, hicimos una demostración del concepto de "Vector C++" utilizando este enfoque.

Agregamos un tipo de datos Vector multidimensional, cuya semántica de almacenamiento es solo que los elementos de la matriz son distintos. Esto es diferente al modelo de ubicaciones secuenciales de C++, pero necesita esta semántica diferente si quiere que el compilador/transformador tenga libertad para distribuir la memoria arbitrariamente, y esto es fundamental si quiere usar instrucciones de la máquina SIMD y/o accesos de caché eficientes. a lo largo de diferentes ejes.

Agregamos acceso de rango escalar y subarreglo de estilo Fortran-90, agregamos prácticamente todas las operaciones de procesamiento de matrices de F90, agregamos una buena fracción de las operaciones de matriz de APL, todo al ajustar la gramática DMS C++.

Finalmente, construimos dos traductores utilizando la capacidad de transformación de DMS: un mapeo de una parte importante de esto (recuerde, esto fue una demostración conceptual) para C++ para poder compilar y ejecutar aplicaciones Vector C++ en una estación de trabajo típica. otro mapeo de C++ a un dialecto PowerPC C++ con extensiones de instrucción SIMD, y generamos un código SIMD que era bastante razonable, pensamos. Nos tomó alrededor de 6 meses hombre para hacer todo esto.

El cliente para esto finalmente se rescató (su modelo comercial no incluía el soporte de un compilador personalizado a pesar de su gran necesidad de operaciones paralelas/basadas en SIMD), y ha estado languideciendo en el estante. Hemos elegido no buscar esto en un mercado más amplio porque no está claro qué es realmente el mercado. Estoy bastante seguro de que hay organizaciones para las cuales esto sería valioso.

El punto es que realmente puedes hacer esto. Es casi imposible usar métodos ad hoc. Es técnicamente bastante sencillo con un sistema de transformación de programa lo suficientemente fuerte. No es un paseo por el parque.

+0

gracias. No puedo usar software no gratuito (proyecto sin fondos académicos) pero encontré algún software (rosa) que parece tener algunas de esas instalaciones – Anycorn

+0

Rose tiene algunas de las capacidades de DMS. Pero usa el front end EDG C++, que AFAIK es un analizador C++ escrito a mano. Injertar los cambios deseados en la interfaz EDG probablemente sea considerablemente más difícil que modificar una gramática (que usa DMS) y puede romper la forma en que el resto de EDG/Rose recopilan datos. No será un paseo en el parque con Rose, tampoco, pero esa es al menos una opción donde tienes un cambio de éxito. Buena suerte. –