你能用C ++制作自定义操作符吗?

时间:2009-10-04 02:04:29

标签: c++ templates operators c-preprocessor metaprogramming

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

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

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

7 个答案:

答案 0 :(得分:31)

是! (好吧,有点)

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

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

  • 宏是邪恶的。如果你犯了一个错误,编译器对于追踪问题几乎完全无用。
  • 即使您获得了正确的宏,如果您对运算符的使用或操作定义中存在错误,编译器也只会稍微有用。
  • 您必须使用有效的标识符作为运营商的一部分。如果您想要一个更像符号的运算符,可以使用_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允许您轻松为同一根标识符指定多个操作

答案 1 :(得分:11)

Sander Stoks在'Syntactic Aspartame'中彻底探讨了一种允许您使用以下格式的方法:

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

本质上,你需要一个带有运算符'&lt;'的代理对象和'&gt;'超载。代理完成所有工作; '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;
}

答案 2 :(得分:2)

为了更准确一点,C ++ 本身仅支持创建现有操作的新重载,而不是创建新的操作符。有些语言(例如ML及其大部分后代)允许您创建全新的运算符,但C ++不是其中之一。

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

答案 3 :(得分:0)

你的建议仅仅是语法糖:

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

实际上已经有一个函数可以在cstring和std :: string中执行此操作。这或许有点像回答“这是一个好主意吗?”但并不完全;而是问“为什么你需要/想要?”

答案 4 :(得分:0)

从技术上讲,没有。也就是说,您无法扩展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还有什么?它本身不能表达。它不能是typename,typedef名称或模板名称。它不能是函数名称。它实际上只能是一个宏,它是唯一剩下的命名实体。为此,请参阅“是(好的,有点)”答案。

答案 5 :(得分:0)

我创建了以下两个宏:

#define define const struct
#define operator(ReturnType, OperatorName, FirstOperandType, SecondOperandType) OperatorName ## _ {} OperatorName; template <typename T> struct OperatorName ## Proxy{public: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

答案 6 :(得分:0)

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

4 to 5

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