定义新的中缀操作符

Defining new infix operators

本文关键字:操作符 中缀 定义      更新时间:2023-10-16

所以多亏了c++ 11,现在可以结合宏,用户定义的字面量,lambdas等来创建最接近"语法糖"的东西。例如

 if (A contains B)

这当然很容易。

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

表达式转换为bool类型,其中contains是一个自定义结构体,将左侧和右侧作为参数。当然,结构体首先重载operator+以接受自定义字符串字面值,然后返回自身,然后返回结构体本身的operator+。

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作为参数,而是在操作符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,这样我就可以将它赋值给一个虚拟变量来执行它。我总是可以做我之前做的result_struct的事情,或者返回一个std::function对象来调用它自己,但我将重复自己。另一个注意事项是,因为宏中有很多const,所以不能修改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) ?

我该如何改进我的代码使其更具可扩展性?就像现在一样,污染很严重。有没有更好的/更普遍的/不那么笨拙的方法来做到这一点?例如,泛化表达式,这样我就不需要定义语句或重用代码。

假设最新的编辑包含了所有的问题,很难看出这里提出的问题是什么。

我是否应该让后缀操作符代替中缀操作符操作符,比如"字符串字面值"。包含"其他字符串字面值",或do it function style, "String literal"。包含("其他字符串文字的)?

是的。"String literal"s.contains("Other string literal"s)是最好的方法-简洁,对c++程序员清楚,对其他语言的程序员清楚(Java和Python字符串有方法),没有模板魔术或宏魔术使用。

我该如何改进我的代码使其更具可扩展性?因为它是正确的现在,污染很严重。有没有更好的/更概括的/更少的笨拙的做法?例如,要概括表达式so我不需要定义语句或重用代码。

是的!但只是在一定程度上(删除了那里和这里不必要的const):

#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:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/
c++/bits/stl_construct.h:63:0,
                 from c:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/
c++/vector:63,
                 from #templateerrors2.cpp:1:
c:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/c++/ext/alloc_traits.h
: In instantiation of 'struct __gnu_cxx::__alloc_traits<std::allocator<void> >':
c:__mojeprogmingwbin../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:__mojeprogmingwbin../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:__mojeprogmingwbin../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:__mojeprogmingwbin../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:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/
c++/vector:65:0,
                 from #templateerrors2.cpp:1:
c:__mojeprogmingwbin../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:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h:
292:7: error: forming reference to void
c:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h:
467:7: error: forming reference to void
c:__mojeprogmingwbin../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:__mojeprogmingwbin../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:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h:
881:7: error: forming reference to void
In file included from c:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/
c++/vector:70:0,
                 from #templateerrors2.cpp:1:
c:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/c++/bits/vector.tcc:10
8:5: error: forming reference to void
In file included from c:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/
c++/vector:65:0,
                 from #templateerrors2.cpp:1:
c:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h:
1003:7: error: forming reference to void
c:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h:
1179:7: error: forming reference to void
In file included from c:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/
c++/vector:70:0,
                 from #templateerrors2.cpp:1:
c:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/c++/bits/vector.tcc:21
6:5: error: forming reference to void
c:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/c++/bits/vector.tcc:43
9:5: error: forming reference to void
c:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/c++/bits/vector.tcc:31
6:5: error: forming reference to void
In file included from c:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/
c++/vector:65:0,
                 from #templateerrors2.cpp:1:
c:__mojeprogmingwbin../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:__mojeprogmingwbin../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:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_vector.h:
161:9: error: invalid use of 'void'
c:__mojeprogmingwbin../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:__mojeprogmingwbin../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:__mojeprogmingwbin../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:__mojeprogmingwbin../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:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/
c++/bits/stl_algobase.h:66:0,
                 from c:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/
c++/vector:61,
                 from #templateerrors2.cpp:1:
c:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_iterator_
base_types.h: In instantiation of 'struct std::iterator_traits<void*>':
c:__mojeprogmingwbin../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:__mojeprogmingwbin../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:__mojeprogmingwbin../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:__mojeprogmingwbin../lib/gcc/mingw32/4.7.2/include/c++/bits/stl_iterator_
base_types.h:182:43: error: forming reference to void