2010-04-11 22 views
7

W języku numerycznym (Matlab, Fortran) operator zakresu i semantyka jest bardzo przydatna podczas pracy z wielowymiarowymi danymi. Na przykład:Operatory w języku C++ specyficzne dla domeny

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

niestety C++ nie ma operatora zakres (:). oczywiście można go emulować za pomocą funktora zakresu/plastra, ale semantyka jest mniej czysta niż Matlaba. Prototypuję język macierzy/języka tensorowego w C++ i zastanawiam się, czy istnieją jakieś opcje odtworzenia operatora zasięgu. Nadal chciałbym polegać wyłącznie na framework C++/prprocessor.

Do tej pory przeanalizowałem falę doładowania, która może być odpowiednią opcją.

Czy są jakieś inne sposoby na wprowadzenie nowych operatorów natywnych do C++ DSL?

Wiem, że nie można dodać nowych operatorów.am specjalnie szuka obejścia. Jedno wymyśliłem (bardzo brzydki hack, a ja nie mam zamiaru używać):

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

Odpowiedz

3

Rozwiązaniem, którego użyłem wcześniej, jest napisanie zewnętrznego preprocesora, który analizuje źródło i zastępuje wszelkie zastosowania niestandardowego operatora za pomocą C++ w wanilii. Dla twoich potrzeb, użycie a : b zostanie zastąpione przez coś takiego jak deklaracje a.operator_range_(b) i operator:() z deklaracjami range_ operator_range_(). W pliku makefile dodaj regułę, która przed skompilowaniem przetwarza pliki źródłowe. Można to zrobić ze względną łatwością w Perlu.

Jednak po pracy z podobnym rozwiązaniem w przeszłości, nie polecam go. Może mieć problemy z utrzymaniem i przenośnością, jeśli nie będziesz czujny, jak przetwarzane i generowane jest źródło.

+0

to jest pomysł. Zasadniczo mogę zastąpić ':' innym binarnym globalnym operatorem, który generuje zasięg i używa ',' do dodania zakresu. Zdaję sobie sprawę z potencjalnych problemów, chcę go wypróbować, jakiegoś szybkiego narzędzia do tworzenia aplikacji. – Anycorn

+0

Powodzenia. Uważaj na awarię w '?:' I etykietach dla specyfikacji dostępu, skrzynek przełączników i (jeśli ich używasz). Chciałbym polecić użycie czegoś takiego jak '..', ponieważ łatwiej byłoby parsować. –

2

Jak powiedział Billy, nie można przeciążać operatorów. Możesz jednak bardzo zbliżyć się do tego, czego chcesz, przez "normalne" przeciążanie operatora (a może przez niektóre metaprogramowanie szablonów). Byłoby dość łatwo pozwolić na coś takiego:

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

Minusem jest to, że to rozwiązanie nie obsługuje wyrażeń wszystko dosłowne. Z lub muszą być klasy zdefiniowane przez użytkownika, ponieważ nie możemy przeciążać operatora - dla dwóch typów pierwotnych.

Można również przeciążać operator* aby umożliwić określenie Stepping, tak:

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

I przeciążać obie wersje operator-- aby umożliwić ignorując jeden granicach:

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

Inną opcją jest określenie dwa obiekty, o nazwie coś w stylu Matrix :: start i Matrix :: end, lub cokolwiek chcesz, a następnie zamiast używać operator--, możesz ich użyć, a następnie druga granica nie będzie musiała być zmienną i może być literał:

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

Można oczywiście użyć obu sposobów.

Uważam, że to najlepsze, co można uzyskać bez uciekania się do dziwnych rozwiązań, takich jak niestandardowe narzędzia do budowania lub nadużycia makr (tego rodzaju Matthieu przedstawił i zasugerował, aby ich nie używać :)).

+0

ktokolwiek przeciąża operatora *, aby użyć go w KONTEKŚCIE NUMERYCZNYM do określenia innego znaczenia, będzie "mylącym kodem wieku" plakatu-chłopca w naszym biurze! Ugh! –

1

Najprostszym rozwiązaniem jest użycie metody na macierzy zamiast operatora.

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

Należy pamiętać, że zazwyczaj nie używać , w operatora indeksem [] np A[i][j] zamiast A[i,j]. Druga forma może być możliwa przez przeciążenie operatora przecinka, ale wtedy wymusisz, aby i i j były obiektami, a nie liczbami.

Można zdefiniować klasę range, która może być używana jako indeks dolny dla klasy macierzy.

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

    // ... 
}; 

Następnie można skopiować zakres z jednej matrycy do drugiej.

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

Wreszcie tworząc klasę Matrix która może pomieścić albo RealMatrix lub MatrixProxy można zrobić RealMatrix i MatrixProxy wyglądają tak samo z zewnątrz.

Uwaga: operator[] na serwerach proxy nie jest i nie może być wirtualny.

0

Jeśli chcesz się dobrze bawić, możesz sprawdzić IdOp.

Jeśli naprawdę pracujesz nad projektem, nie sugeruję używania tej sztuczki. Utrzymanie będzie cierpieć na sprytne sztuczki.

Najlepiej jest więc ugryźć bullet i użyć wyraźnej notacji. Szczególnie przydatna jest krótka funkcja o nazwie range, która zapewnia zdefiniowany obiekt, dla którego operatorzy są przeciążeni.

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; 

Należy zauważyć, że operator[] tylko 4 przeciążenia (const/const + podstawowy INT/zakresie), otrzymuje się obiektu proxy (do ostatniego wymiaru). Po zastosowaniu do ostatniego wymiaru daje widok macierzy. Normalna macierz może być zbudowana z widoku, który ma te same wymiary (nie jawny konstruktor).

+0

Zamiast gry z 'operator []()' wolałbym zdefiniować funkcję członkowską 'Matrix', która pobierałaby podzbiór. Przeciążenie funkcji operatora może spowodować nieoczekiwane problemy. –

+0

Hum, tak, nawet lepiej: p To by zaoszczędziło na pisaniu i zyskało również czytelność! –

2

Alternatywą jest zbudowanie dialektu w warstwie C++ za pomocą narzędzia do transformacji programów.

DMS Software Reengineering Toolkit to silnik do transformacji programów, o sile przemysłowej C++ Front End. DMS, używając tego interfejsu, może parsować pełne C++ (nawet ma preprocesor i może zachować większość nieprecyzyjnych dyrektyw preprocesora), automatycznie buduje AST i kompletuje tablice symboli.

Przedni koniec C++ jest dostępny w źródle, z gramatyką wyprowadzoną bezpośrednio ze standardu. Jest technicznie proste dodawanie nowych reguł gramatycznych, w tym tych, które pozwalałyby na składnię ":" jako indeksów tablicowych, jak to opisałeś, i jak implementował Fortran90 +. Następnie można użyć możliwości transformacji programu DMS, aby przekształcić "nową" składnię w "waniliową" C++ do użycia w konwencjonalnych kompilatorach C++. (Ten schemat jest uogólnieniem modelu programowania intencjonalnego "dodaj pojęcia DSL do twojego języka").

W rzeczywistości przeprowadziliśmy koncepcję demonstracji "Vector C++" z wykorzystaniem tego podejścia.

Dodaliśmy wielowymiarowy wektorowy typ danych, którego semantyką pamięci są tylko te elementy tablicy, które są odrębne. To jest inne niż model sekwencyjnych lokalizacji w C++, ale potrzebujesz tego innego semantycznego, jeśli chcesz, aby kompilator/transformator miał swobodę rozmieszczania pamięci arbitralnie, i to jest fundamentalne, jeśli chcesz używać instrukcji maszynowych SIMD i/lub wydajnych dostępów do pamięci podręcznej. wzdłuż różnych osi.

Dodaliśmy dostęp w skalarach i podstrach w stylu Fortran-90, dodaliśmy praktycznie wszystkie operacje przetwarzania tablic F90, dodano znaczną część operacji macierzy APL, wszystko przez dostosowanie gramatyki DMS C++.

Wreszcie, stworzyliśmy dwóch tłumaczy używających możliwości transformacji DMS: jeden mapował znaczną część tego (pamiętajcie, to było demo koncepcji) do C++ w wanilii, abyście mogli skompilować i uruchomić aplikacje Vector C++ na typowej stacji roboczej, a inne odwzorowanie C++ na dialekt PowerPC C++ z rozszerzeniami instrukcji SIMD, i wygenerowaliśmy kod SIMD, który uważaliśmy za całkiem rozsądny. Zajęło nam to około 6 osobo-miesięcy, żeby to wszystko zrobić.

Klient za to ostatecznie wykupiony (jego model biznesowy nie obejmował obsługi niestandardowego kompilatora pomimo jego ciężkiej potrzeby operacji opartych na równoległych/opartych na SIMD), i to marnieje na półce. Zdecydowaliśmy się nie realizować tego na szerszym rynku, ponieważ nie jest jasne, czym tak naprawdę jest rynek. Jestem prawie pewien, że istnieją organizacje, dla których byłoby to cenne.

Punkt jest, naprawdę możesz to zrobić. Jest to prawie niemożliwe przy użyciu metod ad hoc. Jest to technicznie dość proste z wystarczająco silnym systemem transformacji programu. To nie jest spacer w parku.

+0

dzięki. Nie mogę używać niewolnego oprogramowania (projekt wolny od funduszy akademickich), ale znalazłem oprogramowanie (róża), które wydaje się mieć niektóre z tych urządzeń. – Anycorn

+0

Rose ma pewne możliwości DMS. Używa jednak interfejsu EDG C++, którego AFAIK jest ręcznym parserem C++. Przeszczepienie pożądanych zmian w interfejsie EDG będzie prawdopodobnie znacznie trudniejsze niż modyfikacja gramatyki (z której korzysta DMS) i może przerwać to, jak reszta interfejsu EDG/Rose zbiera dane. To nie będzie spacer w parku z Rose, ale to jest najmniejszy wybór, w którym zmienisz sukces. Powodzenia. –