【问题标题】:Is there a way to convert this "for i" c++ preprocessor macro to modern C++ (11+)?有没有办法将此“for i”c++ 预处理器宏转换为现代 C++(11+)?
【发布时间】:2019-03-26 14:17:35
【问题描述】:

我正在寻找一种方法来用更现代的东西替换这个 C++ 预处理器宏。

#define fori(FORI_TYPE, FORI_FROM, FORI_TO) \
            for(FORI_TYPE i{FORI_FROM}; \
            ((FORI_FROM) < (FORI_TO)) ? (i < (FORI_TO)) : (i > (FORI_TO)); \
            ((FORI_FROM) < (FORI_TO)) ? ++i : --i )

理想情况下,我可以摆脱所有的 ?运算符(constexpr 在这里有用吗?)并且“fori”不会像现在使用处理器版本那样产生任何开销成本(评估 ? 运算符)。另外,键入安全性。

使用示例:

fori(size_t, 0, n)
{
    cout << i << endl;
}

【问题讨论】:

  • 您是否需要遍历范围内的所有数字,或者您打算使用i 作为容器的索引(即您想要遍历项目)?
  • 宏被用在各种各样的上下文中,它可以用作索引或计算,等等。这适用于多项目范围的使用。
  • @Aziuth 改成what,我觉得是重点
  • 只是个人建议:不要。只是不要。宏不好,因为它们难以编写、读取和调试,但如果它们满足您的需求,就使用它们。现代 C++ 元编程肯定比旧的 C 预处理器宏好一点,但代码并不是真的更容易理解或调试……如果你想改变一些东西,教育程序员使用这种语言。使用元编程来只保存几个字符是,呃……我不喜欢的东西。
  • 您可以考虑使用 iota_view 中的 range-v3,它正在添加到 C++20 中

标签: c++ c-preprocessor


【解决方案1】:

对于它的价值,您总是需要知道迭代的方向,因此您无法摆脱这种开销。也就是说,通过切换宏,您至少可以使其更容易优化(部分通过输入const 来促进折叠它们上的重复/类似条件,部分通过预先计算“步长”距离以消除一些完全符合这些条件)。

就宏而言,那个还不错(尽管它可能会使用一个() 或两个...)。

真正“现代”的做法是使用counting iteratorsomething related to irange

例如,天真地改编 Neil 的代码以提供自动步进方向检测:

#include <iostream>

template <class T>
class range
{
private:
    class iter
    {
    private:
        T at, step;
    public:
        iter(T at, T step) : at(at), step(step) {}
        bool operator!=(iter const& other) const { return at != other.at; }
        T const& operator*() const { return at; }
        iter& operator++() { at += step; return *this; }
    };

    T begin_val;
    T end_val;
    T step_val;

public:

    range(const T begin_val, const T end_val)
        : begin_val(begin_val)
        , end_val(end_val)
        , step_val(begin_val > end_val ? -1 : 1)
    {}

    iter begin() { return iter(begin_val, step_val); }
    iter end() { return iter(end_val, step_val); }
};

int main()
{
   for (auto i : range<unsigned>(42, 10))
      std::cout << i << ' ';
   std::cout << '\n';
}

// Output: 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 

(live demo)

或者,坦率地说,您可以编写以下内容并完成它:

#include <iostream>

int main()
{
   for (unsigned int i = 42; i > 10; --i)
      std::cout << i << ' ';
   std::cout << '\n';
}

// Output: 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 

(live demo)

每个人都会明白这一点;不需要任何技巧。

在任何一种情况下,尽管上面有我的例子,I'd actually advise not using unsigned types for this

【讨论】:

  • 你总是需要知道迭代的方向,所以你不能摆脱这个开销 - 如果所有的界限都是在编译时给出的,确实没有理由为什么一个实现不能在没有分支的情况下相应地调整迭代器。您将在编译时需要该分支,但实际上没有理由将其转移到生成的运行时代码中。
  • @ComicSansMS 使用“n”我假设它们在编译时是未知的,否则你当然是对的。
  • 感谢您的澄清!
【解决方案2】:

我们可以通过使用模板来摆脱宏,尽管这确实意味着必须添加一个回调函数。

//f is of the form void func(int i)
template <typename T, typename F>
void fori(const T from, const T to, F f)
{
    for(
        T i  = from;
        (from < to) ? (i < to) : (i > to);
        (form < to) ? ++i : --i 
    ) {
         f(i);
 }

用法:

fori<int>(1, 20, [](int i) {
    std::cout << i << std::endl;
});

这也可以使用 C++20 概念来改进。

【讨论】:

  • 您也可以删除第一个? :,只需将其替换为from != to
【解决方案3】:

要生成一个类型的序列,我们需要使用生成器。 Range for 将使用它的输出:

for(auto i: range<size_t>(0, n)) {
  std::cout << n << " ";
}

还有:

template <typename T> class range {
  T a, b;
public:
  typename wrap<T> begin() const { return a; }
  typename wrap<T> end() const { return b; }
};

包装器会为该值注入一些迭代器应该携带的属性(类型和方法),但对于数字类型来说相当简单。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-05-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多