vector<int> vec{ 1, 2, 3 };
cout << "3 in " << vec << ": " << (3 <in> vec) << '\n'
<< "5 in " << vec << ": " << (5 <in> vec) << '\n';
当然,整个过程必须是通用的,因此任何类似于二进制函数的事物都可以用于定义运算符,例如,
auto in = make_named_operator(
[](int i, vector<int> const& x) {
return find(begin(x), end(x), i) != end(x);
});
我想知道以下实现是否足以处理所有“有趣的” 1情况,以及它是否健壮。例如,我将操作数存储在引用中。那应该行得通,因为它们只能存储到表达式完成之后,因此永远不会过时。我对以下操作符函数的返回类型以及将
<…>
语法用于命名操作符的设计原理的反馈特别感兴趣。2它似乎可以处理模板以及不同的类型(以及cv资格)。
#include <utility>
template <typename F>
struct named_operator_wrapper {
F f;
};
template <typename T, typename F>
struct named_operator_lhs {
F f;
T& value;
};
template <typename T, typename F>
inline named_operator_lhs<T, F> operator <(T& lhs, named_operator_wrapper<F> rhs) {
return {rhs.f, lhs};
}
template <typename T, typename F>
inline named_operator_lhs<T const, F> operator <(T const& lhs, named_operator_wrapper<F> rhs) {
return {rhs.f, lhs};
}
template <typename T1, typename T2, typename F>
inline auto operator >(named_operator_lhs<T1, F> const& lhs, T2 const& rhs)
-> decltype(lhs.f(std::declval<T1>(), std::declval<T2>()))
{
return lhs.f(lhs.value, rhs);
}
template <typename T1, typename T2, typename F>
inline auto operator >=(named_operator_lhs<T1, F> const& lhs, T2 const& rhs)
-> decltype(lhs.value = lhs.f(std::declval<T1>(), std::declval<T2>()))
{
return lhs.value = lhs.f(lhs.value, rhs);
}
template <typename F>
inline constexpr named_operator_wrapper<F> make_named_operator(F f) {
return {f};
}
对于感兴趣的人,完整的示例实现在GitHub上。
1 <在此处插入“有趣的”定义>
2我考虑了其他替代方法,例如R使用的
%…%
,并允许不同的运算符(由Yakk完成)允许不同的运算符优先级但是我决定反对,因为我认为它使运算符优先级比C ++中的要复杂得多。#1 楼
我会随临时人员的移动添加右值参考支持。<op>
的准确度似乎太低,无法实用-您最终必须对(bracket)
进行所有操作(如上所述)。 %至少紧密结合。我喜欢
a <op>= b
。比我的a +op= b
好。将
operator()
从运算符转发到函数让您完全忘记了运算符背后的功能:in(3, vec)
-非常Haskell。推迟应用
f
可使s <append> s2 <append> s3
尽可能高效地运行。但是干净地执行此操作可能会很困难。不确定
inline
的目标是什么。对于一个有趣的测试用例,请实现
(std::future<T> %then% [](T)->U)->std::future<U>
(其中lambda用作占位符) 阻止一些复制并移动ctor以防止持久性,并与适当的运算符交朋友。
如前所述,我允许使用任意二进制运算符(在
make_infix
时选择)将命名运算符括起来:结果命名运算符的前缀与括号运算符完全匹配。因此+append+
的概率为+
,*in*
的概率为*
。在三个用例的第一个用例中(分别为lin alg,container append),其中两个用例是命名运算符,其中现有运算符的变体及其匹配的优先级似乎很有用。评论
\ $ \ begingroup \ $
inline可以使该ODR兼容。我不确定模板是否真正需要此功能(非模板肯定需要),但始终添加它并不会带来伤害。感谢您的反馈,这些是一些有趣的想法。
\ $ \ endgroup \ $
–康拉德·鲁道夫(Konrad Rudolph)
13年2月27日在9:11
\ $ \ begingroup \ $
@KonradRudolph模板始终是内联的(也许没有人使用的extern模板除外)。
\ $ \ endgroup \ $
–右键
13年2月27日在21:53
\ $ \ begingroup \ $
@Zoidberg(完全)专门的模板函数显然确实需要内联。在其他地方也无害
\ $ \ endgroup \ $
– Y牛
13年2月27日在22:07
\ $ \ begingroup \ $
@KonradRudolph因此,我对原始文章进行了编辑,以包括一打使用ADL和标签的名为操作员库的行。 struct op_tag {};静态named_operators :: make_operator
\ $ \ endgroup \ $
– Y牛
15年3月18日在16:12
\ $ \ begingroup \ $
我非常喜欢您的解决方案。我感到需要命名运算符。但是与此有关的一个问题是自动填充器(例如clang格式)会将a
\ $ \ endgroup \ $
– bolov
5月7日12:44
#2 楼
我不是语言律师(仅仅是个涉猎者),但是这个想法很有趣,并且我非常喜欢命名运算符的常见问题。为什么不添加一元运算符?
这将使以下代码成为可能:
c = a <op> b ; // your code
c = !op> a ; // asymmetrical prefix named operator
c = --op> a ; // symmetrical prefix named operator
c = a <op-- ; // symmetrical suffix named operator
我对
<op>
具有(基于怀旧的)偏好,但是没有在C ++中,许多可用的运算符可以是对称的,并且!
和--
的优先级高。另一方面,简单的函数调用已经很好地做到了(但后缀表示法):
c = !op> a ; // asymmetrical prefix named operator
c = op(a) ; // function notation
c = --op> a ; // symmetrical prefix named operator
c = a <op-- ; // symmetrical suffix named operator
...是否有一个域可以清除在旁边写--op>比在旁边写op()?
如果是, ,那么不是一定需要一元运算符吗?
评论
\ $ \ begingroup \ $
“ ...是否有一个域可以比在旁边写op()清除并写--op>?” —我认为这是问题的症结所在,但我不确定情况是否如此(尽管我可以想到潜在的情况,例如矩阵转置)。
\ $ \ endgroup \ $
–康拉德·鲁道夫(Konrad Rudolph)
2015年10月17日在12:09
评论
现在C ++具有非常聪明。但是强大的力量带来了滥用和迷惑的机会。
我认为所有运算符都可能是constexpr。
@LokiAstari:非常聪明。但是,强大的能力带来了滥用和迷惑的机会:这是Java的事情... :-) ...谚语的C ++版本将是“强大的能力带来重大的责任”,我们是工程师,而不是脚本小子...我的猜测是,在某些情况下,它可能非常有用,我很高兴发现Konrad在GitHub上的工作(事实成立两年后),因为它增加了替代方案...