2013-03-26 7 views
23

Quindi grazie a C++ 11, è ora possibile combinare macro, letterali definiti dall'utente, lambda, ecc. Per creare il più vicino possibile allo "zucchero sintattico". Un esempio potrebbe essereDefinizione di nuovi operatori di infisso

if (A contains B) 

Naturalmente questo è facile.

cout <<("hello"_s contains "ello"_s)<<endl; 

L'espressione converte in un bool, dove contiene è una struttura personalizzato che prende il lato sinistro e il lato destro come argomenti. La struct ovviamente sovraccarica l'operatore + a prendere prima la stringa personalizzata letterale, restituendo se stessa, quindi l'operatore + per la struttura stessa.

struct contains_struct { 
    string lhs; 
    string rhs; 
    void set_lhs(string lhs) { this->lhs = lhs; } 
    void set_rhs(string rhs) { this->rhs = rhs; } 
    operator bool() const { 
     return string::npos != lhs.find(rhs); 
    } 
} contains_obj; 

contains_struct& operator+(const string& lhs, const contains_struct& rhs) { 
    contains_obj.set_lhs(lhs); 
    return contains_obj; 
} 

contains_struct& operator+(const contains_struct& lhs, const string& rhs) { 
    contains_obj.set_rhs(rhs); 
    return contains_obj; 
} 

#define contains +contains_obj+ 

Ora ho deciso che voglio andare oltre. Che dire di

(x in a) perform cube 

Non è una lista di comprensione, ma è un buon esempio, giusto? All'inizio ho detto, beh, dovrei andare su StackOverflow per chiedere la precedenza degli operatori personalizzati, ma è semplice metterlo tra parentesi dato che nessuno sano di mente userebbe il mio codice. Invece, ho ampliato il mio altro esempio e ho "in" e "perform" come strutture personalizzate, proprio come "contiene".

Puoi andare oltre e stamparlo in modo che x possa essere un qualsiasi indice numerico e un qualsiasi contenitore, ma per semplicità ho lasciato x come numero intero e come vettore di valori. Ora, finora, non prende la variabile locale x come argomento, la utilizza localmente nella funzione string() dell'operatore.

Per semplificare le cose, ho memorizzare i risultati dell'espressione in una stringa, in questo modo

operator string() const { 
    string s = ""; 
    for (int x : lhs.rhs) 
     s += to_string(rhs(x)) + string("\n"); 
    return s; 
} 

Grazie ad un'altra domanda: Overloading assignment operator for type deduction

ho capito un'utilità pratica per la restituzione come un incarico è il seguente:

struct result_struct { 
    vector<int> results; 
    result_struct(vector<int> results) { this->results = results; } 
}; 

... 

    operator result_struct() const { 
     vector<int> tmp; 
     for (int x : lhs.rhs) 
      tmp.push_back(rhs(x)); 
     return result_struct(tmp); 
    } 

... 

result_struct result_2 = (x in a) perform cube; 
    for (int x : result_2.results) 
     cout <<x<<endl; 

Grazie a milleniumbug's answer, posso fare:

struct for_obj 
{ 
    int _lhs; 
    std::vector<int> _rhs; 
    for_obj(int lhs, std::vector<int> rhs) 
     : _lhs(lhs), _rhs(rhs) { } 
}; 

INFIX_OPERATOR(for_obj, in_op, int, std::vector<int>) 
{ 
    return for_obj(lhs(), rhs()); 
} 
#define in + in_op() + 

INFIX_OPERATOR(int, perform_op, for_obj, std::function<int(int)>) 
{ 
    for (int i = 0; i < lhs()._rhs.size(); i++) 
     rhs()(lhs()._rhs[i]); 
    return 0; 
} 
#define perform + perform_op() + 

Ci sono due avvertimenti. Innanzitutto, restituisco un int in modo che possa assegnarlo a una variabile dummy per farlo eseguire. Potrei sempre fare la cosa result_struct che ho fatto prima, o restituire un oggetto std :: function per chiamarlo da solo, ma mi ripeterò. L'altra avvertenza è che poiché ci sono così tante conte nella macro, non è possibile modificare lhs (che non consente di specificare un iteratore).

Tutto considerato, il seguente funziona come previsto.

int x = 0; 
std::vector<int> nums = { 1, 2, 3 }; 
auto cube = [] (int x) 
{ 
    std::cout << x * x * x << std::endl; 
    return x * x * x; 
}; 
int i = (x in nums) perform cube; 

Nuova versione

class PerformObj { 
    int counter; 
public: 
    PerformObj() : counter(0) { } 
    ~PerformObj() { } 
    InObj lhs; 
    std::function<int(int)> rhs; 

    operator int() const { 
     return rhs(lhs.rhs[counter]); 
    } 
} performobj; 

#define perform + performobj + 

PerformObj& operator+(const InObj& lhs, PerformObj& rhs) { 
    rhs.lhs = lhs; 
    return rhs; 
} 

PerformObj& operator+(PerformObj& lhs, const std::function<int(int)>& rhs) { 
    lhs.rhs = rhs; 
    return lhs; 
} 

int main() 
{ 
    std::vector<int> nums = {1,2,3}; 
    int x = 0; 

    auto cube = [] (int n) { 
     return n * n * n; 
    }; 

    std::cout << x in nums perform cube << std::endl; 
} 

explicit operator std::vector<int>() const { 
    std::vector<int> temp; 
    for (int i = 0; i < lhs.rhs.size(); i++) { 
     temp.push_back(rhs(lhs.rhs[i])); 
    } 
    return temp; 
} 

int y = 0; 
std::cout << y in static_cast<std::vector<int>>(x in nums perform cube) perform std::function<int(int)>([] (int i) -> int { 
     return i; 
}) << std::endl; 

Devo fare in modo che invece di operatori infissi, ci sono operatori in forma suffissa, come "String literal"s.contains "Other string literal"s, o farlo funzione di stile, "String literal"s.contains("Other string literal"s) ?

Come potrei migliorare il mio codice per renderlo più estensibile? Come è adesso, è molto inquinato. C'è un modo migliore/più generalizzato/meno goffo per farlo? Ad esempio, per generalizzare le espressioni in modo che non sia necessario definire istruzioni o riutilizzare il codice.

+14

Questa è una buona tecnica per l'offuscamento, ma dovrebbe sicuramente essere evitato altrimenti Rende impossibile per qualcuno che conosce il C++ leggere il tuo codice. –

+0

Sembra che tu stia reinventando i modelli di espressione ... cerca il termine e dovresti trovare molto. Ma per quanto riguarda l'introduzione di nuovi operatori infissi nel linguaggio, cattiva idea. Attacca con la notazione di chiamata di funzione o perde popolarità. – Potatoswatter

+4

Sto inviando questo messaggio perché è interessante e accurato, ma sicuramente non lo sostengo per l'uso effettivo nel codice reale. –

risposta

11

È difficile vedere quale è la domanda posta qui, assumendo che l'ultima modifica abbia tutte le domande.

Devo fare in modo che invece di operatori infissi, ci sono postfix operatori, come s "String letterale" s.contains "Altro letterale di stringa", o farlo stile funzione "letterale di stringa" s .contains ("Altra stringa letterale" s)?

Sì. "String literal"s.contains("Other string literal"s) è il modo migliore - chiaro, chiaro per i programmatori C++, chiaro per i programmatori di altri linguaggi (le stringhe di Java e Python hanno metodi) e nessun modello magico o magia macro è usato.

Come potrei migliorare il mio codice per renderlo più estensibile? Come è giusto ora, è molto inquinato. C'è un modo più semplice/più generalizzato/meno per farlo? Ad esempio, per generalizzare le espressioni in modo da che non è necessario definire le istruzioni o riutilizzare il codice.

Sì! Ma solo per certo (rimosso le const inutili laggiù e qui):

#define INFIX_OPERATOR(rettype, name, LT, RT) \ 
struct name\ 
{\ 
private:\ 
    LT* left;\ 
    RT* right;\ 
\ 
protected:\ 
    LT& lhs() const { return *left; }\ 
    RT& rhs() const { return *right; }\ 
\ 
public: \ 
    friend name operator+(LT& lhs, name && op)\ 
    {\ 
     op.left = &lhs;\ 
     return op;\ 
    }\ 
\ 
    friend name operator+(name && op, RT& rhs)\ 
    {\ 
     op.right = &rhs;\ 
     return op;\ 
    }\ 
\ 
    name() : left(nullptr), right(nullptr) {}\ 
\ 
    operator rettype() const;\ 
};\ 
\ 
inline name :: operator rettype() const 

E poi si può creare il vostro operatore infisso in questo modo:

#include <iostream> 
#include <string> 

INFIX_OPERATOR(bool, contains_op, const std::string, const std::string) 
{ 
    return std::string::npos != lhs().find(rhs()); 
} 
#define contains + contains_op() + 

int main() 
{ 
    std::string a = "hello"; 
    std::string b = "hell"; 
    if(a contains b) 
     std::cout << "YES"; 
} 

Si noti che non v'è alcun modo per evitare # define contiene direttive, poiché non c'è modo di creare una direttiva macro con un'altra direttiva macro.

Quali sono i vantaggi pratici di questo se ci sono (ignorando tutti razionalità di utilizzo di questo codice mondo reale. Voglio dire, che cosa si può ottenere fuori di esso per quello che sto usando per, il blocco delle chiamate Dire che il mio amico, invece di imparare il C++, vuole un'interfaccia semplice astratta per la sua esperienza Bash o Perl, ma vorrebbe collaborare con senza ricorrere alla compilazione/collegamento al di fuori di gcc. Quel modo , può scrivere "script" o "codice" che è C++, e compilare e collegarlo con i miei programmi/librerie/interfaccia, qualunque cosa.

Sembra che si stia tentando di creare una lingua in un'altra lingua.Prepararsi per

  • ore e ore cercando di testare la vostra lingua.
  • Messaggi di diagnostica imbarazzanti. Prova a compilare questo: std::vector<void> myarr; Quindi avvolgere con macro. E poi avvolgilo in un altro modello. E poi in un'altra macro ... hai avuto l'idea.
  • Strumenti di debug che mostrano il codice elaborato.
  • Anche se la tua lingua si integra perfettamente con se stessa, hai ancora C++ di cui occuparti, con un sacco di regole e un complicato sistema di tipi. After all, all abstractions are leaky.

Se il tuo amico desidera programmare in Perl, lasciaglielo fare. Queste lingue sono facilmente interfacciabili con C.

Se si sta tentando di creare una lingua, perché le altre lingue non possono esprimere in modo pulito ciò che si sta tentando di fare, generatori di parser (Flex/Bison, ANTLR) e LLVM lo rende facile.

Se la creazione di un parser è eccessiva, date un'occhiata ai mix di lingua D. Accettano una stringa creata in fase di compilazione e quindi la compilano come se fosse stata inserita direttamente.

Qui ...

import std.stdio; 
int main() 
{ 
    mixin(`write("Hello world");`); //`contents` is a raw string literal 
    return 0;      //so is r"contents" 
} 

è equivalente a:

import std.stdio; 
int main() 
{ 
    write("Hello world"); 
    return 0; 
} 

Questo è solo un semplice esempio. Si potrebbe avere la funzione che analizza una stringa:

mixin(user1508519s_language(r"(x in a) perform cube")); 

- Ecco come appare (gcc 4.7.2):

In file included from c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/ 
c++/bits/stl_construct.h:63:0, 
       from c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/ 
c++/vector:63, 
       from #templateerrors2.cpp:1: 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/ext/alloc_traits.h 
: In instantiation of 'struct __gnu_cxx::__alloc_traits<std::allocator<void> >': 

c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h: 
76:28: required from 'struct std::_Vector_base<void, std::allocator<void> >' 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h: 
208:11: required from 'class std::vector<void>' 
#templateerrors2.cpp:5:19: required from here 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/ext/alloc_traits.h 
:189:53: error: no type named 'reference' in 'class std::allocator<void>' 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/ext/alloc_traits.h 
:190:53: error: no type named 'const_reference' in 'class std::allocator<void>' 
In file included from c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/ 
c++/vector:65:0, 
       from #templateerrors2.cpp:1: 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h: 
In instantiation of 'class std::vector<void>': 
#templateerrors2.cpp:5:19: required from here 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h: 
292:7: error: forming reference to void 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h: 
467:7: error: forming reference to void 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h: 
684:7: error: invalid parameter type 'std::vector<void>::value_type {aka void}' 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h: 
684:7: error: in declaration 'void std::vector<_Tp, _Alloc>::resize(std::vector< 
_Tp, _Alloc>::size_type, std::vector<_Tp, _Alloc>::value_type)' 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h: 
881:7: error: forming reference to void 
In file included from c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/ 
c++/vector:70:0, 
       from #templateerrors2.cpp:1: 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/vector.tcc:10 
8:5: error: forming reference to void 
In file included from c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/ 
c++/vector:65:0, 
       from #templateerrors2.cpp:1: 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h: 
1003:7: error: forming reference to void 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h: 
1179:7: error: forming reference to void 
In file included from c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/ 
c++/vector:70:0, 
       from #templateerrors2.cpp:1: 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/vector.tcc:21 
6:5: error: forming reference to void 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/vector.tcc:43 
9:5: error: forming reference to void 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/vector.tcc:31 
6:5: error: forming reference to void 
In file included from c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/ 
c++/vector:65:0, 
       from #templateerrors2.cpp:1: 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h: 
In instantiation of 'std::_Vector_base<_Tp, _Alloc>::~_Vector_base() [with _Tp 
= void; _Alloc = std::allocator<void>]': 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h: 
247:15: required from 'std::vector<_Tp, _Alloc>::vector() [with _Tp = void; _A 
lloc = std::allocator<void>]' 
#templateerrors2.cpp:5:19: required from here 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h: 
161:9: error: invalid use of 'void' 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h: 
In instantiation of 'void std::_Vector_base<_Tp, _Alloc>::_M_deallocate(std::_V 
ector_base<_Tp, _Alloc>::pointer, std::size_t) [with _Tp = void; _Alloc = std::a 
llocator<void>; std::_Vector_base<_Tp, _Alloc>::pointer = void*; std::size_t = u 
nsigned int]': 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h: 
161:9: required from 'std::_Vector_base<_Tp, _Alloc>::~_Vector_base() [with _T 
p = void; _Alloc = std::allocator<void>]' 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h: 
247:15: required from 'std::vector<_Tp, _Alloc>::vector() [with _Tp = void; _A 
lloc = std::allocator<void>]' 
#templateerrors2.cpp:5:19: required from here 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h: 
175:4: error: 'struct std::_Vector_base<void, std::allocator<void> >::_Vector_im 
pl' has no member named 'deallocate' 
In file included from c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/ 
c++/bits/stl_algobase.h:66:0, 
       from c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/ 
c++/vector:61, 
       from #templateerrors2.cpp:1: 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_iterator_ 
base_types.h: In instantiation of 'struct std::iterator_traits<void*>': 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_construct 
.h:127:24: required from 'void std::_Destroy(_ForwardIterator, _ForwardIterato 
r) [with _ForwardIterator = void*]' 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_construct 
.h:155:7: required from 'void std::_Destroy(_ForwardIterator, _ForwardIterator 
, std::allocator<_T2>&) [with _ForwardIterator = void*; _Tp = void]' 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h: 
403:9: required from 'std::vector<_Tp, _Alloc>::~vector() [with _Tp = void; _A 
lloc = std::allocator<void>]' 
#templateerrors2.cpp:5:19: required from here 
c:\__moje\prog\mingw\bin\../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_iterator_ 
base_types.h:182:43: error: forming reference to void