【问题标题】:What is a good alternative to this C++17 fold expression in C++14?在 C++14 中这个 C++17 折叠表达式有什么好的替代方法?
【发布时间】:2018-06-23 23:45:29
【问题描述】:

这是一个漂亮、简洁的 C++17 中基于折叠表达式的 lambda:

#include <cstdint>

using ::std::uint64_t;

constexpr auto sumsquares = [](auto... n) { return ((n * n) + ...); };

// I want this to work.
uint64_t foo(uint64_t x, uint64_t y, uint64_t z)
{
    return sumsquares(x, y, z);
}
// And this too
double bar(uint64_t x, double y)
{
    return sumsquares(x, y);
}

我编写了这段代码,用于在 C++14 中执行类似的操作,但它似乎比应有的更冗长和混乱。我正在寻找一种以相对清晰和简洁的方式在 C++14 中表达上述 C++17 代码的方法。准确地说,我希望能够编写代码,使用类似函数调用的语法来计算某个已知维数的向量的向量幅度的平方。但是,维度的数量可以任意变化。并且坐标系的各个分量的精确数字类型也可能是任意的,并且可能是异构的。但是在 C++14 中处理 C++17 折叠表达式的一般方法是理想的。

#include <cstdint>
#include <utility> 

using ::std::uint64_t;

namespace {
    static constexpr struct {
        template <typename T>
        auto operator()(T && n) const
        {
           return n*n;
        }
        template <typename T, typename... S>
        auto operator()(T && n, S && ... s) const
        {
            return (n * n) + (*this)(::std::forward<S>(s)...);
        }
    } sumsquares;
}

// I want this to work.
uint64_t foo(uint64_t x, uint64_t y, uint64_t z)
{
    return sumsquares(x, y, z);
}
// And this too
double bar(uint64_t x, double y)
{
    return sumsquares(x, y);
}

【问题讨论】:

  • 它应该做什么?
  • @NeilButterworth - 我希望能够用简洁的函数调用类语法来表达计算任意数量的类数字对象的平方和。每次我需要这个总和时,我都知道在那个特定时间我将处理多少个数字。我希望编译器为此生成尽可能高效的代码。 :-)
  • @Omnifarious 我认为您的问题的正确位置是:codereview.stackexchange.com :)
  • @eyllanesc - 是的,我想知道那不是一个更好的地方。 :-/
  • 您可以将参数分解成一个共同类型的数组,然后使用常规循环来计算结果。

标签: c++ templates c++14 variadic-templates fold-expression


【解决方案1】:
#include <utility>
#include <functional>

template<class F, class A0>
auto fold(F&&, A0&& a0) {
    return std::forward<A0>(a0);
}

template<class F, class A0, class...As>
auto fold(F&& f, A0&&a0, As&&...as) {
    return f(std::forward<A0>(a0), fold(f, std::forward<As>(as)...));
}

auto sum_squares=[](auto&&...args) {
    return fold(std::plus<>{}, (args * args)... );
};

【讨论】:

  • 我觉得不太对。 return f(std::forward&lt;A0&gt;(a0),fold(std::forward&lt;As&gt;(as)...)); 应为 return f(std::forward&lt;A0&gt;(a0),fold(std::forward&lt;F&gt;(f), std::forward&lt;As&gt;(as)...));。但是,否则,这是我的新宠。它提供了一种将几乎所有大小写折叠表达式转换为 C++14 的方法。
  • @omni 抱歉,是在电影的片头字幕中输入的;没有编译它。将尝试修复它。好的固定;电影结束。应该是右折叠还是左折叠,但我不想错,需要检查定义才能确定。
  • 这个解决方案模式有名字吗?
  • @solico 你能从函数名中猜出它的名字吗?
【解决方案2】:

sum() 的一种可能的非递归方式如下

template <typename ... Ts>
auto sum (Ts ... ts)
 {
   using unused = int[];

   std::common_type_t<Ts...>  ret {};

   (void)unused{ 0, (ret += ts, 0)... };

   return ret;
 }

给定一个sum()函数,sum_of_squares()可以简单写成如下

template <typename ... Ts>
auto sum_of_squares (Ts ... ts)
 { return sum( (ts*ts)... ); }

【讨论】:

  • 其中一个问题是返回类型。它假定第一个参数的类型是适当的返回类型,在异构类型的情况下可能不是。
  • @Omnifarious - 你是对的;使用std::common_type 怎么样?答案已修改。
  • 我认为您避免递归的方法绝对很有趣(这也是我第一次看到它),但是it seems to lead to less optimal assembly,我个人觉得它的可读性有点差
  • @Synxis - 很常用的方法,专门开发C++14 constexpr可变参数模板函数(在C++11中不能用于constexpr函数,在C++中17 如果经常用模板折叠代替)。但我同意:它的可读性有点差。关于最佳组装……有趣;我不知道。但是,这可能取决于特定的编译器。
  • 我不高度重视删除递归作为提高可读性的途径。我发现 Haskell 完全不可读,但那是因为完全没有任何标点符号,而不是因为递归。我发现 Scheme 相当易读,并且它同样使用递归。我认为学习阅读递归的东西是中级及以上技能水平的程序员应该具备的核心技能。
【解决方案3】:

是的,您实际上可以通过在多个对象上定义一个通用总和来做更简单和更通用的操作:

template<typename T>
T sum(T x)
{
    return x;
}
template<typename U, typename... T>
auto sum(U x, T... nums)
{
    return x + sum(nums...);
}

template<typename... T>
auto sum_of_squares(T... nums)
{
    auto square = [](auto x) { return x * x; };
    return sum(square(nums)...);
}

Live demo herehere 你可以看到生成的程序集(这似乎是最佳的)。虽然我个人非常不喜欢 auto 作为返回类型,但在这里它可以避免非常复杂的表达式来获取类型。

【讨论】:

  • 那肯定更好。谢谢。
  • 我同意你对auto 的看法。但是,我编写原始 C++17-only fold-based lambda 的部分原因是为了看看我可以在表达式中使用多少次 auto 并让它实际上是正确的做法。 :-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-21
  • 2019-08-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多