Определение новых инфиксных операторов

Итак, благодаря C ++ 11 теперь можно комбинировать макросы, определяемые пользователем литералы, лямбда-выражения и т. Д. Для создания максимально приближенного к «синтаксическому сахару». Примером может быть

 if (A contains B)

Конечно, это легко.

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

Выражение преобразуется в логическое значение, где содержит настраиваемую структуру, которая принимает левую и правую части в качестве аргументов. Структура, конечно, перегружает оператор +, чтобы сначала принять настраиваемый строковый литерал, вернув сам себя, а затем оператор + для самой структуры.

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+

Теперь я решил, что хочу пойти дальше. Как насчет

(x in a) perform cube

Это не понимание списка, но это довольно хороший пример, верно? Сначала я сказал, что мне нужно перейти в stackoverflow, чтобы спросить о приоритете настраиваемых операторов, но это просто поместить в круглые скобки, поскольку никто в здравом уме не будет использовать мой код. Вместо этого я расширил свой другой пример и добавил 'in' и 'perform' в качестве настраиваемых структур, точно так же, как 'contains'.

Вы можете пойти дальше и создать шаблон, чтобы x мог быть любым числовым индексом, а a - любым контейнером, но для простоты я оставил x как целое число, а a как вектор целых чисел. Пока что он фактически не принимает локальную переменную x в качестве аргумента, он использует ее локально в функции operator string ().

Для упрощения я сохраняю результаты выражения в строке, например так

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

Благодаря другому вопросу: Перегрузка оператора присваивания для вывода типа

Я понял, что одним из практических способов использования его в качестве задания является следующее:

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;

Благодаря ответу milleniumbug я могу:

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() +

Есть два предостережения. Во-первых, я возвращаю int, чтобы я мог назначить его фиктивной переменной, чтобы заставить ее выполнить. Я всегда мог сделать то, что делал раньше, или вернуть объект std :: function, чтобы вызвать его сам по себе, но я бы повторил себя. Другое предостережение заключается в том, что, поскольку в макросе так много констант, вы не можете изменять lhs (что не позволяет вам указывать итератор).

Учитывая все обстоятельства, следующее работает, как ожидалось.

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;

Новая версия

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;

Должен ли я сделать так, чтобы вместо инфиксных операторов использовались постфиксные операторы, такие как "String literal"s.contains "Other string literal"s, или сделать это в стиле функции, "String literal"s.contains("Other string literal"s)?

Как мне улучшить свой код, чтобы сделать его более расширяемым? Сейчас он очень загрязнен. Есть ли лучший / более обобщенный / менее неуклюжий способ сделать это? Например, чтобы обобщить выражения, чтобы мне не нужно было определять операторы или повторно использовать код.


person Community    schedule 26.03.2013    source источник
comment
Это хороший метод обфускации, но в противном случае его следует категорически избегать. Это делает невозможным чтение вашего кода тем, кто знает C ++.   -  person James Kanze    schedule 26.03.2013
comment
Кажется, вы заново изобретаете шаблоны выражений ... посмотрите термин, и вы найдете много. А вот вводить в язык новые инфиксные операторы - плохая идея. Придерживайтесь обозначения вызова функций или потеряйте популярность.   -  person Potatoswatter    schedule 26.03.2013
comment
Я поддерживаю это, потому что это интересно и аккуратно, но я определенно не защищаю его для фактического использования в реальном коде.   -  person Jamin Grey    schedule 28.03.2013
comment
ужасный взлом. если вам нужен язык сценариев, используйте его.   -  person Walter    schedule 01.04.2013
comment
Как бы то ни было, это стало возможным еще до C ++ 11. См .: stackoverflow.com/questions / 1515399 /   -  person Cogwheel    schedule 04.09.2015


Ответы (1)


Трудно понять, какой вопрос здесь задается, если предположить, что в последней редакции есть все вопросы.

Должен ли я сделать так, чтобы вместо инфиксных операторов использовались постфиксные операторы, такие как «Строковый литерал» s.contains «Другой строковый литерал» s, или сделать это в стиле функции «Строковый литерал» s.contains («Другой строковый литерал» с)?

да. "String literal"s.contains("Other string literal"s) - лучший способ - краткий, понятный для программистов на C ++, понятный для программистов других языков (строки Java и Python имеют методы), и не используется магия шаблонов или магия макросов.

Как мне улучшить свой код, чтобы сделать его более расширяемым? Сейчас он очень загрязнен. Есть ли лучший / более обобщенный / менее неуклюжий способ сделать это? Например, чтобы обобщить выражения, чтобы мне не нужно было определять операторы или повторно использовать код.

Ага! Но только до определенной степени (удалили ненужные константы там и здесь):

#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

А затем вы можете создать свой инфиксный оператор следующим образом:

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

Обратите внимание, что нет способа избежать директивы #define contains, так как нет способа создать директиву макроса с другой директивой макроса.

Каковы практические преимущества этого, если они есть (игнорируя всю рациональность использования этого кода как реального мира. Я имею в виду, что вы можете получить от этого для того, для чего я его использую, за исключением развлекательных целей?) Скажите, что мой друг вместо изучения C ++ хочет иметь простой абстрагированный интерфейс для работы с Bash или Perl, но хотел бы сотрудничать, не прибегая к компиляции / компоновке вне gcc. Таким образом, он может писать «сценарии» или «код» на C ++, а также компилировать и связывать его с моими программами / библиотеками / интерфейсом, что угодно.

Кажется, что вы пытаетесь создать язык поверх другого языка. Подготовиться к

  • Часы и часы, пытаясь проверить свой язык.
  • Досадно плохие диагностические сообщения. Попробуйте скомпилировать это: std::vector<void> myarr; 1 Затем оберните его макросами. А затем заверните его в другой шаблон. А потом в другом макросе ... Вы поняли.
  • Инструменты отладки, показывающие обработанный код.
  • Даже если ваш язык отлично интегрируется сам с собой, вам все равно придется позаботиться о C ++ с множеством правил и сложной системой типов. В конце концов, все абстракции негерметичны.

Если ваш друг хочет программировать на Perl, просто позвольте ему это сделать. Эти языки легко взаимодействуют с C.

Если вы пытаетесь создать язык, потому что другие языки не могут четко выразить то, что вы пытаетесь сделать, генераторы парсеров (Flex / Bison, ANTLR) и LLVM упрощают это.

Если создание синтаксического анализатора является излишним, взгляните на миксины языка D. Они принимают строку, созданную во время компиляции, а затем компилируют ее, как если бы она была вставлена ​​напрямую.

Здесь...

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

эквивалентно:

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

Это всего лишь простой пример. У вас может быть ваша функция, которая анализирует строку:

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

1 - Вот как это выглядит (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
person milleniumbug    schedule 29.03.2013