你能用 C++ 创建自定义运算符吗?

发布于 2024-08-06 19:55:34 字数 139 浏览 3 评论 0原文

是否可以创建一个自定义运算符,以便您可以执行类似的操作?

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

注意:这是一个与“这是一个好主意吗……”不同的问题;)

Is it possible to make a custom operator so you can do things like this?

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

Note: this is a separate question from "Is it a good idea to..." ;)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

ㄟ。诗瑗 2024-08-13 19:55:34

是的! (嗯,有点)

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

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

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

开头为 纯粹无聊的练习成为我自己对这个问题的看法。这是一个类似的例子:

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 允许您轻松地为同一根标识符指定多个操作

Yes! (well, sort of)

There are a couple publicly available tools to help you out. Both use preprocessor code generation to create templates which implement the custom operators. These operators consist of one or more built-in operators in conjunction with an identifier.

Since these aren't actually custom operators, but merely tricks of operator overloading, there are a few caveats:

  • Macros are evil. If you make a mistake, the compiler will be all but entirely useless for tracking down the problem.
  • Even if you get the macro right, if there is an error in your usage of the operator or in the definition of your operation, the compiler will be only slightly more helpful.
  • You must use a valid identifier as part of the operator. If you want a more symbol-like operator, you can use _, o or similarly simple alphanumerics.

CustomOperators

While I was working on my own library for this purpose (see below) I came across this project. Here is an example of creating an avg operator:

#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

What started as an exercise in pure frivolity became my own take on this problem. Here's a similar example:

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_>

Key Differences

  • CustomOperators supports postfix unary operators
  • IdOp templates use references rather than pointers to eliminate use of the free store, and to allow full compile-time evaluation of the operation
  • IdOp allows you to easily specify several operations for the same root identifier
小傻瓜 2024-08-13 19:55:34

Sander Stoks 在'句法阿斯巴甜' 中彻底探讨了一种方法,它允许您使用格式如下:

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

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

// 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;
}

There's a method thoroughly explored in 'Syntactic Aspartame' by Sander Stoks that would allow you to use the following format:

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

In essence, you need a proxy object with the operators '<' and '>' overloaded. The proxy does all of the work; 'contains' can just be a singleton with no behavior or data of its own.

// 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;
}
心凉 2024-08-13 19:55:34

我创建了以下两个宏:

#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

警告

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

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

I've created the following two macros:

#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)

Then, you'd have just to define your custom operator as in the following example:

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

Once a time you've set your operator up, you can use it as a predefined operator:

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

Warning

While this has been an interesting exercise, it merely demonstrates how bad is to have a macro–enabled precompiler. Adding custom operators like this can easily lead to a sort of metalanguage. Although we know how badly is C++ designed (most of all considering that it was first conceived as a set of extensions for C), we shouldn't be changing it. If you can't use standard C++, which is the only way to keep the code understandable by other people, you should just switch to another language that makes what you wish to do the way you'd like. There are thousands languages — no need to mess around with C++ to make it different.

SHORTLY: You just shouldn't be using this code. You should refrain from using macros unless when only used the same way as inline methods.

千柳 2024-08-13 19:55:34

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

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

To be a bit more accurate, C++ itself only supports creating new overloads of existing operations, NOT creating new operators. There are languages (e.g., ML and most of its descendants) that do allow you to create entirely new operators, but C++ is not one of them.

From the looks of things, (at least) the CustomOperators library mentioned in the other answer doesn't support entirely custom operators either. At least if I'm reading things correctly, it's (internally) translating your custom operator into an overload of an existing operator. That makes things easier, at the expense of some flexibility -- for example, when you create a new operator in ML, you can give it precedence different from that of any built-in operator.

永不分离 2024-08-13 19:55:34

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

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

Technically, no. That is to say, you can't extend the set of operator+, operator-, etcetera. But what you're proposing in your example is something else. You are wondering if there is a definition of "contains" such that string-literal "contains" string-literal is an expression, with non-trivial logic (#define contains "" being the trivial case).

There are not many expressions that can have the form string-literal X string-literal. This is because string literals themselves are expressions. So, you're looking for a language rule of the form expr X expr. There are quite a few of those, but they're all rules for operators, and those don't work on strings. Despite the obvious implementation, "Hello, " + "world" is not a valid expression. So, what else can X be in string-literal X string-literal ? It can't be a expression itself. It can't be a typename, a typedef name or a template name. It can't be a function name. It can really only be a macro, which are the only remaining named entities. For that, see the "Yes (well, sort of)" answer.

宣告ˉ结束 2024-08-13 19:55:34

正如其他人指出的那样,遗憾的是您无法编写自定义运算符,但使用宏您可以获得类似的行为。实际上,使用 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。

As others have pointed out you sadly can not write custom operators but with macros you can get similar behaviour. It is actually really easy with c style casting see below.


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)

Here 4 to 5 will give back an object of type To_Range. (Slicing_To_End) casts 5 to Slicing_To_End. Now the compiler wants to find an == operator that fits. The only one is our custom operator that takes as input an integer on the first position and in the second Slicing_To_End and returns our type To_Range. You also could return of course other types like int,float.

z祗昰~ 2024-08-13 19:55:34

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

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

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

Your suggestion would be nothing more than syntactic sugar for:

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

and in fact there are already a functions to do that in both cstring and std::string. Which is perhaps a bit like answering "is it a good idea?" but not quite; rather asking "why would you need/want to?"

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文