【发布时间】:2010-12-03 16:15:43
【问题描述】:
是否可以制作自定义运算符,以便您可以执行此类操作?
if ("Hello, world!" contains "Hello") ...
注意:这是一个独立于“...是个好主意”的问题;)
【问题讨论】:
标签: c++ templates operators c-preprocessor metaprogramming
是否可以制作自定义运算符,以便您可以执行此类操作?
if ("Hello, world!" contains "Hello") ...
注意:这是一个独立于“...是个好主意”的问题;)
【问题讨论】:
标签: c++ templates operators c-preprocessor metaprogramming
有几个公开可用的工具可以帮助您。两者都使用预处理器代码生成来创建实现自定义运算符的模板。这些运算符由一个或多个内置运算符以及标识符组成。
由于这些实际上不是自定义运算符,而只是运算符重载的技巧,因此有一些警告:
_、o 或类似的简单字母数字。当我为此目的开发自己的库时(见下文),我遇到了这个项目。以下是创建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)
以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_>
【讨论】:
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;
}
【讨论】:
我创建了以下两个宏:
#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++ 来使其与众不同。
简短:您不应该使用此代码。除非仅以与内联方法相同的方式使用,否则应避免使用宏。
【讨论】:
scontains 的运算符时,它将两个字符串作为LHS 和RHS 操作数并返回a.find(b) != std::string::npos 的布尔值,它给出了错误“cannot convert ‘std::string {aka std::basic_string}’ to ‘bool’ in initialization”。这可能是什么原因,有解决办法吗?
更准确地说,C++本身只支持创建现有操作的新重载,不支持创建新操作符。有些语言(例如 ML 及其大多数后代)确实允许您创建全新的运算符,但 C++ 不是其中之一。
从外观上看,(至少)另一个答案中提到的 CustomOperators 库也不完全支持自定义运算符。至少如果我正确阅读,它(内部)将您的自定义运算符转换为现有运算符的重载。这让事情变得更容易,但牺牲了一些灵活性——例如,当您在 ML 中创建一个新运算符时,您可以赋予它不同于任何内置运算符的优先级。
【讨论】:
从技术上讲,没有。也就是说,你不能扩展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 还能是什么?它本身不可能是一种表达方式。它不能是类型名称、类型定义名称或模板名称。它不能是函数名。它实际上只能是一个宏,这是唯一剩下的命名实体。为此,请参阅“是(嗯,有点)”答案。
【讨论】:
operator* 添加重载。你不能做的是添加operator@。 C++ 标准完全指定了存在哪些运算符,并且只有那些可以用新类型的参数重载。
正如其他人指出的那样,遗憾的是您不能编写自定义运算符,但使用宏您可以获得类似的行为。使用 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。
【讨论】:
您的建议只不过是语法糖:
if( contains( "Hello, world!", "Hello" ) ...
事实上,在 cstring 和 std::string 中已经有一个函数可以做到这一点。这可能有点像回答“这是个好主意吗?”但不完全;而不是问“你为什么需要/想要?”
【讨论】: