Yakk的帖子使我想到了C ++中的命名运算符的想法。这看起来很棒(尽管非常不合常规)。例如,可以使以下代码轻松编译:

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 ++中的要复杂得多。

评论

现在C ++具有运算符

非常聪明。但是强大的力量带来了滥用和迷惑的机会。

我认为所有运算符都可能是constexpr。

@LokiAstari:非常聪明。但是,强大的能力带来了滥用和迷惑的机会:这是Java的事情... :-) ...谚语的C ++版本将是“强大的能力带来重大的责任”,我们是工程师,而不是脚本小子...我的猜测是,在某些情况下,它可能非常有用,我很高兴发现Konrad在GitHub上的工作(事实成立两年后),因为它增加了替代方案...

#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 op;然后当lhs * op * rhs发生在启用ADL的上下文中时,调用invoke(lhs,op_tag,rhs)。意味着您不需要使用函数对象,并且拥有完整的重载机制:有人甚至可以在自己的名称空间中为自己的类型重载运算符。似乎比单功能对象方法干净得多。
\ $ \ endgroup \ $
– Y牛
15年3月18日在16:12

\ $ \ begingroup \ $
我非常喜欢您的解决方案。我感到需要命名运算符。但是与此有关的一个问题是自动填充器(例如clang格式)会将a b很好地弄混成 b。我发现唯一使空格缩进的是递增/递减运算符:a --op-- b。还有其他解决方案吗?
\ $ \ 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