【问题标题】:Element-wise tuple addition逐元素元组加法
【发布时间】:2018-06-12 10:14:17
【问题描述】:

我有一些值保存在一个元组中,我希望按元素向它添加另一个元组。所以我想要这样的功能:

std::tuple<int,int> a = {1,2};
std::tuple<int,int> b = {2,4};


std::tuple<int,int> c = a + b; // possible syntax 1
a += b; // possible syntax 2
a += {2,4}; // possible syntax 3

输出元组的值为{3,6}

正在查看CPP reference,但我找不到此功能。 thisthis 问题可能是相关的,但是答案被其他复杂性混淆了。

【问题讨论】:

  • 重载operator+ 来处理您需要的情况如何?
  • 你也可以考虑给你的元组一个名字(即一个专用的类),因为你现在将它们用于其他用途,而不仅仅是保存数据。

标签: c++ tuples


【解决方案1】:

您也可以考虑使用std::valarray,因为它完全允许您似乎想要的东西。

#include <valarray>

int main()
{
    std::valarray<int> a{ 1, 2 }, b{ 2, 4 }, c;
    c = a - b; // c is {-1,-2}
    a += b; // a is {3,6}
    a -= b; // a is {1,2} again
    a += {2, 4}; // a is {3,6} again
    return 0;
}

【讨论】:

  • 现在你很少看到std::valarray。赞成。明显的缺点是它是动态的,这与std::tuple 相反。
  • 这似乎是一个很好的解决方案。不利的一面是,如果我们有std::valarray&lt;int&gt; d{2,4,3};,那么a -= d; // a is {1, 2}d -= b; // d is {3, 0, 0},有些人可能会觉得这种行为违反直觉
  • 你是对的。当两个 valarray 的大小相同时,它工作得很好。 d -= b 可能会更糟,因为 d 的大小为 3,而第三个值可能最终成为垃圾。
【解决方案2】:

你可以使用这样的东西,它支持你所有的三个语法建议:

#include <tuple>
#include <utility>

namespace internal
{
    //see: https://stackoverflow.com/a/16387374/4181011
    template<typename T, size_t... Is>
    void add_rhs_to_lhs(T& t1, const T& t2, std::integer_sequence<size_t, Is...>)
    {
        auto l = { (std::get<Is>(t1) += std::get<Is>(t2), 0)... };
        (void)l; // prevent unused warning
    }
}

template <typename...T>
std::tuple<T...>& operator += (std::tuple<T...>& lhs, const std::tuple<T...>& rhs)
{
    internal::add_rhs_to_lhs(lhs, rhs, std::index_sequence_for<T...>{});
    return lhs;
}

template <typename...T>
std::tuple<T...> operator + (std::tuple<T...> lhs, const std::tuple<T...>& rhs)
{
   return lhs += rhs;
}

工作示例:

http://coliru.stacked-crooked.com/a/27b8cf370d44d3d5

http://coliru.stacked-crooked.com/a/ff24dae1c336b937


在大多数情况下,我仍然会使用命名结构。 元组很少是正确的选择。

【讨论】:

  • 很好,但我的理解是:你为什么不做std::tuple&lt;T...&gt; operator+ (std::tuple&lt;T...&gt; lhs, const std::tuple&lt;T...&gt;&amp; rhs) { return lhs += rhs; }
  • 请参阅this question,了解为什么如果您需要使用副本,最好按值传递。
  • @JHBonarius 因为这样我就不需要在辅助函数中处理返回值了。实现 operator+= 更简单......
  • 如果您使用的是 c++17,请考虑使用[[maybe_unused]] auto l 来抑制未使用的警告。如需更多信息,请参阅attributes page on cppreference
  • @KorbenDose 我不明白这在这里如何应用。
【解决方案3】:

@lubgr 的解决方案满足您的特定用例。我为您提供了一个通用的解决方案,它适用于不同类型的元组,以及不同(相等)大小的元组。

#include <tuple>
#include <utility>
#include <iostream>

template<typename... T1, typename... T2, std::size_t... I>
constexpr auto add(const std::tuple<T1...>& t1, const std::tuple<T2...>& t2, 
                   std::index_sequence<I...>)
{
    return std::tuple{ std::get<I>(t1) + std::get<I>(t2)... };
}

template<typename... T1, typename... T2>
constexpr auto operator+(const std::tuple<T1...>& t1, const std::tuple<T2...>& t2)
{
    // make sure both tuples have the same size
    static_assert(sizeof...(T1) == sizeof...(T2));

    return add(t1, t2, std::make_index_sequence<sizeof...(T1)>{});
}

我使用了一些 C++17 特性(主要是与模板相关的),如果没有这些特性,代码会变得有点复杂。一个可能的改进是利用移动语义。

显然,这适用于您提供的第一个“可能的语法”。

【讨论】:

  • 也许考虑将operator+ 的参数指定为元组,这样它就不会与其他通用operator+ 定义发生冲突。 template&lt;typename... Args&gt; constexpr auto operator+(const std::tuple&lt;Args...&gt;&amp; t1, const std::tuple&lt;Args...&gt;&amp; t2)。这样你也不需要断言两个元组的大小相同。
  • @KorbenDose 谢谢你的建议。已编辑。我虽然需要断言,因为我想拥有例如std::tuple&lt;int, int&gt;std::tuple&lt;int, double&gt; 是可添加的,这需要单独的 typename 参数。
  • 我明白了,非常有趣的想法。我更喜欢这种方式!
【解决方案4】:

这是语法 #1 的运算符定义:

template <class S, class T> std::tuple<S, T> operator + (const std::tuple<S, T>& lhs, const std::tuple<S, T>& rhs)
{
   return std::make_tuple(std::get<0>(lhs) + std::get<0>(rhs), std::get<1>(lhs) + std::get<1>(rhs));
}

如果不创建自定义结构,语法 #2 和 #3 是不可能的,因为它们只能定义为它们所操作的类的成员(并且您不能触及命名空间 std 中的现有类)。

【讨论】:

  • 这确实回答了这个问题,但是您对这如何适用于任意长度(和类型)的元组有任何想法吗?
  • @PrunusPersica 见DeiDei's answer,此解决方案适用于任何长度相等的元组。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-12-20
  • 2010-10-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-28
  • 2016-06-04
相关资源
最近更新 更多