您可以在 C++ 中制作自定义运算符吗?

Posted

技术标签:

【中文标题】您可以在 C++ 中制作自定义运算符吗?【英文标题】:Can you make custom operators in C++? 【发布时间】:2010-12-03 16:15:43 【问题描述】:

是否可以制作自定义运算符,以便您可以执行此类操作?

if ("Hello, world!" contains "Hello") ...

注意:这是一个独立于“...是个好主意”的问题;)

【问题讨论】:

【参考方案1】:

是的! (嗯,有点)

有几个公开可用的工具可以帮助您。两者都使用预处理器代码生成来创建实现自定义运算符的模板。这些运算符由一个或多个内置运算符以及标识符组成。

由于这些实际上不是自定义运算符,而只是运算符重载的技巧,因此有一些警告:

宏是邪恶的。如果你犯了一个错误,编译器将几乎完全无法追踪问题。 即使你得到了正确的宏,如果你在使用运算符或在你的操作定义中出现错误,编译器也只会稍微有帮助。 您必须使用有效的标识符作为运算符的一部分。如果您想要一个更像符号的运算符,您可以使用_o 或类似的简单字母数字。

CustomOperators

当我为此目的开发自己的库时(见下文),我遇到了这个项目。以下是创建avg 运算符的示例:

#define avg BinaryOperatorDefinition(_op_avg, /)
DeclareBinaryOperator(_op_avg)
DeclareOperatorLeftType(_op_avg, /, double);
inline double _op_avg(double l, double r)

   return (l + r) / 2;

BindBinaryOperator(double, _op_avg, /, double, double)

IdOp

以an exercise in pure frivolity 开头的东西变成了我自己对这个问题的看法。这是一个类似的例子:

template<typename T> class AvgOp  
public: 
   T operator()(const T& left, const T& right) 
   
      return (left + right) / 2; 
   
;
IDOP_CREATE_LEFT_HANDED(<, _avg_, >, AvgOp)
#define avg <_avg_>

主要区别

CustomOperators 支持后缀一元运算符 IdOp 模板使用引用而不是指针来消除对免费存储的使用,并允许对操作进行完整的编译时评估 IdOp 允许您轻松地为同一个根标识符指定多个操作

【讨论】:

一个警告:由于预处理阶段发生在编译之前,任何与这些自定义运算符相关的错误消息都可能很难与您编写的代码相关联,因为编译错误将是无论您的代码变成什么。不是说你不应该这样做(如果适合你的问题),而是尽量少用——这会让你的生活变得困难。 听起来很酷。听起来很聪明。我脑后的某些东西告诉我“你做错了”和“自定义运算符被故意排除在语言规范之外。” @Michael Kohne:完全同意。在过去的几天里,我有一些令人抓狂的调试经历。 @Bob Kaufman:是的,它作为一种新奇事物可能比什么都好,但如果它有助于使你的代码更清晰,它可能是一个 Good Thing TM。 我猜想定义新运算符的能力被排除在语言规范之外,因为它使编写 C++ 解析器变得非常困难(而且开始时已经非常困难了)。您必须处理运算符优先级、关联性等。【参考方案2】:

Sander Stoks 在'Syntactic Aspartame' 中彻底探索了一种方法,可让您使用以下格式:

if ("Hello, world!" <contains> "Hello") ...

本质上,您需要一个重载运算符“”的代理对象。代理完成所有工作; 'contains' 可以只是一个没有自己的行为或数据的单例。

// Not my code!
const struct contains_  contains;

template <typename T>
struct ContainsProxy

    ContainsProxy(const T& t): t_(t) 
    const T& t_;
;

template <typename T>
ContainsProxy<T> operator<(const T& lhs, const contains_& rhs)

    return ContainsProxy<T>(lhs);


bool operator>(const ContainsProxy<Rect>& lhs, const Rect& rhs)

    return lhs.t_.left   <= rhs.left && 
           lhs.t_.top    <= rhs.top && 
       lhs.t_.right  >= rhs.right && 
       lhs.t_.bottom >= rhs.bottom;

【讨论】:

这篇文章很好地展示了我的答案中的两个库是如何工作的。 我认为这是 boost spirit 的做法。【参考方案3】:

我创建了以下两个宏:

#define define const struct
#define operator(ReturnType, OperatorName, FirstOperandType, SecondOperandType) OperatorName ## _  OperatorName; template <typename T> struct OperatorName ## Proxypublic:OperatorName ## Proxy(const T& t) : t_(t)const T& t_;static ReturnType _ ## OperatorName ## _(const FirstOperandType a, const SecondOperandType b);;template <typename T> OperatorName ## Proxy<T> operator<(const T& lhs, const OperatorName ## _& rhs)return OperatorName ## Proxy<T>(lhs);ReturnType operator>(const OperatorName ## Proxy<FirstOperandType>& lhs, const SecondOperandType& rhs)return OperatorName ## Proxy<FirstOperandType>::_ ## OperatorName ## _(lhs.t_, rhs);template <typename T> inline ReturnType OperatorName ## Proxy<T>::_ ## OperatorName ## _(const FirstOperandType a, const SecondOperandType b)

然后,您只需定义自定义运算符,如下例所示:

define operator(bool, myOr, bool, bool)  // Arguments are the return type, the name of the operator, the left operand type and the right operand type, respectively
    return a || b;


#define myOr <myOr> // Finally, you have to define a macro to avoid to put the < and > operator at the start and end of the operator name

一旦您设置了运算符,您就可以将其用作预定义的运算符:

bool a = true myOr false;
// a == true

警告

虽然这是一个有趣的练习,但它只是展示了启用宏的预编译器是多么糟糕。添加像这样的自定义运算符很容易导致一种元语言。尽管我们知道 C++ 的设计有多糟糕(最重要的是考虑到它最初是作为 C 的一组扩展而设想的),但我们不应该改变它。如果您不能使用标准 C++,这是保持代码被其他人理解的唯一方法,您应该切换到另一种语言,让您希望按照自己喜欢的方式进行操作。有数千种语言 - 无需乱用 C++ 来使其与众不同。

简短:您不应该使用此代码。除非仅以与内联方法相同的方式使用,否则应避免使用宏。

【讨论】:

这就像黄金一样,真的太神奇了。可怜的家伙为此等了9年。他领先于时代。 虽然这太棒了(我什至从不相信这样的事情是可能的),但我遇到了一些困难。当我设置scontains 的运算符时,它将两个字符串作为LHS 和RHS 操作数并返回a.find(b) != std::string::npos 的布尔值,它给出了错误“cannot convert ‘std::string aka std::basic_string’ to ‘bool’ in initialization”。这可能是什么原因,有解决办法吗? @mediocrevegetable1,如果没有完整的代码,很难理解您的问题。但是,我的代码应该可以工作,并且确实适用于很多人,所以我认为这是你的问题。然而,这段代码永远不应该被使用——我只是在我的回答中添加了一个解释。 @DavideCannizzo 当然,我非常怀疑我是否会在现实生活中使用自定义运算符。我只是在玩耍,看看我能用它做些什么,只是因为这是一件很酷的事情。至于显示代码,我不确定我真的可以把它放在哪里(我不认为我可以将我的所有代码粘贴到评论部分!),但如果你知道一个地方,那么我将非常感激。 @mediocrevegetable1,至于显示您的代码,您可能想在 Code Review 上提出您自己的问题,包括我的答案中您用来制作自定义运算符的两个宏。然后,如果您在此处添加指向该问题的链接作为评论,我可以尝试回答您。【参考方案4】:

更准确地说,C++本身只支持创建现有操作的新重载,不支持创建新操作符。有些语言(例如 ML 及其大多数后代)确实允许您创建全新的运算符,但 C++ 不是其中之一。

从外观上看,(至少)另一个答案中提到的 CustomOperators 库也不完全支持自定义运算符。至少如果我正确阅读,它(内部)将您的自定义运算符转换为现有运算符的重载。这让事情变得更容易,但牺牲了一些灵活性——例如,当您在 ML 中创建一个新运算符时,您可以赋予它不同于任何内置运算符的优先级。

【讨论】:

我在原始答案中添加了澄清/警告。谢谢:)【参考方案5】:

从技术上讲,没有。也就是说,你不能扩展operator+operator-等的集合。但是你在你的例子中提出的是另一回事。您想知道是否存在“包含”的定义,使得 string-literal "contains" string-literal 是一个表达式,具有非平凡的逻辑(#define contains "" 是平凡的情况)。

没有多少表达式可以具有string-literal X string-literal 的形式。这是因为字符串文字本身就是表达式。因此,您正在寻找expr X expr 形式的语言规则。其中有很多,但它们都是运算符的规则,并且不适用于字符串。尽管有明显的实现,"Hello, " + "world" 不是一个有效的表达式。那么,string-literal X string-literal 中的 X 还能是什么?它本身不可能是一种表达方式。它不能是类型名称、类型定义名称或模板名称。它不能是函数名。它实际上只能是一个宏,这是唯一剩下的命名实体。为此,请参阅“是(嗯,有点)”答案。

【讨论】:

我不知道“扩展”在这种情况下是什么意思,但你绝对可以在 C++ 中定义 + 和 - 运算符。 @Andy:显然。您还可以为operator* 添加重载。你不能做的是添加operator@。 C++ 标准完全指定了存在哪些运算符,并且只有那些可以用新类型的参数重载。 哦,现在我明白你之前的意思了。是的,您不能定义自己的自定义运算符。【参考方案6】:

正如其他人指出的那样,遗憾的是您不能编写自定义运算符,但使用宏您可以获得类似的行为。使用 c 样式转换实际上非常简单,请参见下文。


class To_Range
public:
    size_t start;
    size_t end;
    To_Range(size_t _start,size_t _end) :
    start(_start), end(_end) 

;

class Slicing_To_End
public:
    int end;
    Slicing_To_End(const int& init) : end(init) 
;

To_Range operator == (const int& start,const Slicing_To_End& end) 
    return To_Range(start,end.end);


#define to == (Slicing_To_End)

这里4 to 5 将返回一个To_Range 类型的对象。 (Slicing_To_End) 将 5 转换为 Slicing_To_End。现在编译器想要找到一个适合的 == 运算符。唯一的一个是我们的自定义运算符,它将第一个位置和第二个 Slicing_To_End 的整数作为输入,并返回我们的类型 To_Range。当然,您也可以返回其他类型,例如 int、float。

【讨论】:

【参考方案7】:

您的建议只不过是语法糖:

if( contains( "Hello, world!", "Hello" ) ...

事实上,在 cstring 和 std::string 中已经有一个函数可以做到这一点。这可能有点像回答“这是个好主意吗?”但不完全;而不是问“你为什么需要/想要?”

【讨论】:

嗯,这只是我在被告知将帖子拆分为问题/答案时编造的一个任意示例。 ;) 话虽如此,语法糖正是重点。我喜欢 C++,因为你可以用无数种方式来表达问题的解决方案(过程、功能、oo 等)。这些工具使您能够更进一步地尽可能自然地表示概念。当然,清醒的用途也更少(如 IdOp 示例所示)。 :P 实际上,avg 示例(我从 CustomOperators 页面复制而来)可能是我不会使用此类内容的地方。当您考虑平均值时,您会想到“……的平均值”。这使得 avg(x, y) 比“x avg y”更合适。 “包含”语言(我也在 CustomOperators 页面上找到)更好地说明了这个特定的结构。 我认为像“你为什么要这样做”这样的 cmets 完全适得其反。显然OP想要这样做。质疑他为什么要这样做不是任何人的事。对于通过搜索关键字找到这样的帖子,然后寄希望于为自己的问题找到答案的人来说,这也是非常令人沮丧的,然后就在你的脸上回答“你为什么要这样做” .幸运的是,这里有一些建设性的答案。 我是否说过“你为什么曾经想要这样做?”我可能会同意;但我并不那么不屑一顾。相反,它旨在作为考虑收益与努力的建议。我记得我正在处理 OP 关于这个问题的评论不是明确的“这是一个好主意吗?”的问题。这可能是一个好主意,但工作量很大,可能收效甚微。此外,答案是 6 年前发布的;我今天可能会更恰当地将其发布为评论。 OP当时评论并澄清了他的意图。我当时注意到要添加到现有答案中。

以上是关于您可以在 C++ 中制作自定义运算符吗?的主要内容,如果未能解决你的问题,请参考以下文章

您可以使用界面生成器在 xcode 中制作自定义视图吗?

自定义模板运算符

无法在具有自定义运算符 ==() 的 c++ 无序集中找到用户定义的类型

Scala 自定义运算符(示例 abs)

可以在 Python 中制作自定义字符串文字前缀吗?

我可以在 Google 地图上放置自定义对象吗?