【问题标题】:Condensing a do-while loop to a #define macro将 do-while 循环压缩为 #define 宏
【发布时间】:2013-12-29 07:55:14
【问题描述】:

考虑以下示例代码(我实际上使用更长的二进制字符串,但这足以解释问题):

  void enumerateAllSubsets(unsigned char d) {
       unsigned char n = 0;
       do {
           cout<<binaryPrint(n)<<",";
       } while ( n = (n - d) & d );
    }

函数(由于 Knuth)有效地循环遍历二进制字符串的所有子集;

例如:

33 = '00100001' in binary and enumerateAllSubsets(33) would produce:

00000000, 00100000, 00000001, 00100001.

我需要编写一个#define 来实现

   macroEnumerate(n,33)
        cout<<binaryPrint(n)<<",";

行为方式等同于 enumerateAllSubsets(33)。 (好吧,顺序可能会重新排列)

基本上我需要能够对集合的子集执行各种操作。

用 for 循环做类似的事情是微不足道的:

for(int i=0;i < a.size();i++)
    foo(a[i]);

可以替换为:

 #define foreach(index,container) for(int index=0;index < container.size();index++)
    ...
    foreach(i,a)
        foo(a[i]);

enumerateAllSubsets() 的问题是循环体需要无条件执行一次,因此 do-while 不能被重写为 for。

我知道这个问题可以通过 STL 风格的模板函数和传递给它的 lambda(类似于 STL for_each 函数)来解决,但是一些糟糕的 #define 宏似乎是一个更干净的解决方案。

【问题讨论】:

  • 我很少会认为宏比函数更干净。
  • “我需要写一个#define ...”——不,你真的不需要(或想要)那个。这是一个可怕的想法。

标签: c++ c macros metaprogramming c-preprocessor


【解决方案1】:

假设 C++11,定义一个范围对象:

#include <iostream>
#include <iterator>
#include <cstdlib>

template <typename T>
class Subsets {
public:
    Subsets(T d, T n = 0)   : d_(d), n_(n) { }
    Subsets begin() const { return *this; }
    Subsets end() const { return {0, 0}; }

    bool operator!=(Subsets const & i) const { return d_ != i.d_ || n_ != i.n_; }

    Subsets & operator++() {
        if (!(n_ = (n_ - d_) & d_)) d_ = 0;
        return *this;
    }

    T operator*() const { return n_; }

private:
    T d_, n_;
};

template <typename T>
inline Subsets<T> make_subsets(T t) { return Subsets<T>(t); }

int main(int /*argc*/, char * argv[]) {
    int d = atoi(argv[1]);
    for (auto i : make_subsets(d))
        std::cout << i << "\n";
}

如果你想使用它,我已经把它写得很笼统了,例如,uint64_t

【讨论】:

  • 我更改了代码以消除使用 -1 作为终止条件。
【解决方案2】:

一种选择是使用始终至少运行一次的 for 循环,例如:

for (bool once = true; once? (once = false, true) : (n = (n - d) & d); )
    // loop body

在第一次迭代中,once 变量被清除并且表达式的计算结果为真,因此循环执行。从那时起,实际的测试和步骤逻辑将控制循环。

从这里开始,将其重写为宏应该会容易得多。

希望这会有所帮助!

【讨论】:

  • 这很简洁,唯一的问题是'n'需要在for循环之外声明。
【解决方案3】:

你可以做一个使用表达式的多行宏,像这样:

#define macroenum(n, d, expr ) \
    n = 0;                     \
    do {                       \
        (expr);                \
    } while (n = (n -d) & d)   \
        ;                      \

int main(int argc, const char* argv[])
{

    enumerateAllSubsets(33);

    int n;
    macroenum(n, 33, cout << n << ",");
}

正如其他人所提到的,这不会被许多人认为是非常干净的 - 除其他外,它依赖于范围内存在的变量“n”。您可能需要将 expr 包装在另一组括号中,但我使用 g++ 对其进行了测试,并得到了与 enumerateAllSubsets 相同的输出。

【讨论】:

  • 您实际上可以在 #define 中声明 n 而不是在调用者的范围内进行。类似于 foreach(index,container) 中的索引。
【解决方案4】:

您的目标似乎是能够执行enumerateAllSubsets 之类的操作,但要更改每次迭代执行的操作。

在 C++ 中,您可以使用头文件中的函数来执行此操作:

template<typename Func>
inline void enumerateAllSubsets(unsigned char d, Func f)
{
   unsigned char n = 0;
   do { f(n); } while ( n = (n - d) & d );
}    

示例用法:

enumerateAllSubsets(33, [](auto n) { cout << binaryPrint(n) << ','; } );

【讨论】:

    猜你喜欢
    • 2015-02-16
    • 1970-01-01
    • 1970-01-01
    • 2018-01-16
    • 2020-08-28
    • 1970-01-01
    • 1970-01-01
    • 2018-11-21
    • 2021-05-22
    相关资源
    最近更新 更多