在定义可交换操作时减少代码重复

Posted

技术标签:

【中文标题】在定义可交换操作时减少代码重复【英文标题】:Reducing code duplication while defining a commutative operation 【发布时间】:2017-12-10 03:12:34 【问题描述】:

我有一组名为overlap 的可交换二进制函数的重载,它接受两种不同的类型:

class A a; class B b;
bool overlap(A, B);
bool overlap(B, A);

当且仅当一个形状与另一个形状重叠时,我的函数 overlap 返回 true - 这是讨论 multimethods 时使用的一个常见示例。

因为overlap(a, b) 等价于overlap(b, a),所以我只需要实现关系的一个“边”。一种重复的解决方案是这样写:

bool overlap(A a, B b)  /* check for overlap */ 
bool overlap(B b, A a)  return overlap(a, b);   

但我不希望通过允许使用模板生成相同的函数来编写额外的N! / 2 琐碎版本。

template <typename T, typename U> 
bool overlap(T&& t, U&& u) 
 return overlap(std::forward<U>(u), std::forward<T>(t)); 

不幸的是,这很容易无限递归,这是不可接受的:见 http://coliru.stacked-crooked.com/a/20851835593bd557

如何防止这种无限递归?我是否正确地解决了这个问题?

【问题讨论】:

相关:***.com/questions/30561407/… -- 将参数放在一个元组中,然后使用 std::get() 以“正确”的顺序将它们拉出。 与狭窄的范围分开,我预计在许多情况下需要这样做是代码异味。例如,A 和 B 是否应该不实现形状接口/从父类继承? 拥有一个由你的两个类实现的接口不是有意义的吗,所以你真的不需要关心 A 类和 B 类吗?或者这在 C++ 中不是那么容易吗? 【参考方案1】:

这是一个简单的解决方法:

template <typename T, typename U> 
void overlap(T t, U u)

    void overlap(U, T);
    overlap(u, t);

模板本身声明了目标函数,这将优于递归,因为它是完全匹配的(确保在您的实际案例中注意常量性和引用性)。如果该功能尚未实现,则会出现链接器错误:

/tmp/cc7zinK8.o: In function `void overlap<C, D>(C, D)':
main.cpp:(.text._Z7overlapI1C1DEvT_T0_[_Z7overlapI1C1DEvT_T0_]+0x20):
    undefined reference to `overlap(D, C)'
collect2: error: ld returned 1 exit status

...直接指向缺失的函数:)

【讨论】:

那么,在这种情况下,您是否需要声明第二个检查重叠的函数? @Vitor 你是什么意思? 我们从bool overlap(A a, B b) /* check for overlap */ bool overlap(B b, A a) return overlap(a, b); 这两个函数开始。现在你提出了一个单一的功能,但我没有看到任何代码来检查重叠。我不明白这个技巧。 :s 注意:在我看来这仅适用于同一命名空间中的overlap 函数。 直到链接时间才检测到错误对于具有许多源文件的大型项目来说可能是一个很大的缺点。更改.h 可能会导致大量文件需要重新编译,因此链接时间可能需要几分钟的编译时间。希望这很容易解决,并且在大多数代码库中不需要经常重新调整。【参考方案2】:

您可以将实际方法重命名为overlap_impl 之类的名称,然后在模板中调用它。我将打破递归:

bool overlap_impl(A a, B b)  /* check for overlap */ 

template <typename T, typename U> 
bool overlap(T&& t, U&& u) 
 return overlap_impl(std::forward<U>(u), std::forward<T>(t)); 

template<> bool overlap(A&& t, B&& u)
 return overlap_impl(std::forward<A>(t), std::forward<B>(u)); 

【讨论】:

您仍然需要将“正确的”呼叫转发到 overlapoverlap_impl 好吧,我猜这需要模板专业化来处理正确的情况。 您可以添加一些 SFINAE 来为所有类型创建两个模板函数,它们会自动确定传递参数的顺序。【参考方案3】:

正如一位智者曾经说过的,没有什么问题是通过额外的间接层解决不了的,除了太多的间接层。

所以,使用 SFINAE 和一些间接来完成它:

template<class A, class B>
auto overlap(A&& a, B&& b)
-> decltype(overlap_impl('\0', std::forward<A>(a), std::forward<B>(b)))
 return overlap_impl('\0', std::forward<A>(a), std::forward<B>(b)); 

template<class A, class B>
auto overlap_impl(int, A&& a, B&& b)
-> decltype(do_overlap(std::forward<A>(a), std::forward<B>(b)))
 return do_overlap(std::forward<A>(a), std::forward<B>(b)); 

template<class A, class B>
auto overlap_impl(long, B&& b, A&& a)
-> decltype(do_overlap(std::forward<A>(a), std::forward<B>(b)))
 return do_overlap(std::forward<A>(a), std::forward<B>(b)); 

// You can provide more choices if you want, for example to use member-functions.

// Implement `do_overlap(A, B)`, maybe with references, in at least one direction.

【讨论】:

它非常对称。我喜欢它。 @cat:如果我可以使用 TS 的概念,也许吧。

以上是关于在定义可交换操作时减少代码重复的主要内容,如果未能解决你的问题,请参考以下文章

测试 KtorClient 时减少代码重复

如何使用 Angular Decorator 减少重复代码?

代码质量第 2 层 - 可重用的代码

函数的初识

Python学习示例源码

通过多个 OAuth2 提供程序登录时如何减少代码重复?