如何在 C++ (c++11/c++17) 中执行元组运算?
How to perform tuple arithmetic in C++ (c++11/c++17)?
我正在尝试编写模板 functions/operators,例如 +
,用于在相同类型的两个元组之间进行算术运算。例如,对于
std::tuple<int,double> t = std::make_tuple(1,2);
我愿意
auto t1 = t + t;
逻辑很简单:按元素进行算术运算。但我无法弄清楚如何在 c++ 模板编程 (c++11/17) 中完成这项工作。我的以下代码无法使用 g++ -std=c++11 tuple_arith.cpp
进行编译。特别是,我想不出让通用 add
函数 (template<typename T> T add(T x, T y) { return x + y; }
) 与元组操作代码一起工作的正确方法。
有人可以帮忙解释一下如何解决这个问题吗?
#include <tuple>
namespace std {
template<typename _Tp, size_t __i, size_t __size, typename _opT >
struct __tuple_arith {
static constexpr _Tp __op(const _Tp& __t, const _Tp& __u, const _opT& op) {
return std::tuple_cat(std::make_tuple(op(std::get<__i>(__t), std::get<__i>(__u))
, __tuple_arith<_Tp, __i + 1, __size, _opT>::__op(__t, __u)));
}
};
template<typename _Tp, size_t __size, typename _opT>
struct __tuple_arith<_Tp, __size, __size - 1, _opT> {
static constexpr _Tp __op(const _Tp& __t, const _Tp& __u, const _opT& op) {
return std::make_tuple(op(std::get<__size-1>(__t), std::get<__size -1>(__u)));
}
};
template<typename T> T add(T x, T y) { return x + y; }
template<typename... _TElements> constexpr tuple<_TElements...>
operator+(const tuple<_TElements...>& __t, const tuple<_TElements...>& __u) {
using op = __tuple_arith<tuple<_TElements...>, 0, sizeof...(_TElements), decltype(add)>;
return op::__op(__t, __u, add);
}
}; //namespace std
#include <iostream>
using namespace std;
int main() {
std::tuple<int,double> t = std::make_tuple(1,2);
auto t1 = t + t;
cout << std::get<0>(t1) << std::endl;
return 0;
}
具体错误为:
tuple_arith.cpp:14:10: error: template argument ‘(__size - 1)’ involves template parameter(s)
struct __tuple_arith<_Tp, __size, __size - 1, _opT> {
^
tuple_arith.cpp: In function ‘constexpr std::tuple<_Elements ...> std::operator+(const std::tuple<_Elements ...>&, const std::tuple<_Elements ...>&)’:
tuple_arith.cpp:24:90: error: decltype cannot resolve address of overloaded function
__tuple_arith<tuple<_TElements...>, 0, sizeof...(_TElements), decltype(add)>;
^
tuple_arith.cpp:24:91: error: template argument 4 is invalid
__tuple_arith<tuple<_TElements...>, 0, sizeof...(_TElements), decltype(add)>;
^
tuple_arith.cpp:25:12: error: ‘op’ has not been declared
return op::__op(__t, __u, add);
^
tuple_arith.cpp: In instantiation of ‘constexpr std::tuple<_Elements ...> std::operator+(const std::tuple<_Elements ...>&, const std::tuple<_Elements ...>&) [with _TElements = {int, double}]’:
tuple_arith.cpp:34:17: required from here
tuple_arith.cpp:26:3: error: body of constexpr function ‘constexpr std::tuple<_Elements ...> std::operator+(const std::tuple<_Elements ...>&, const std::tuple<_Elements ...>&) [with _TElements = {int, double}]’ not a return-statement
}
^
--更新--
感谢您到目前为止提供的有用答案。是否有可能使其适用于任何 Operator Wrappers,例如std::{plus,minus,multiplies,divides}
?这就是我试图通过模板参数 typename _opT
实现的目标。最后,我正在寻找一个可以将兼容的运算符作为参数的function/object。
您的代码中的问题是您不能基于另一个模板值部分特化一个模板值;你可以解决这个问题,但是......为什么?
用std::index_sequence
获得想要的东西还不简单
#include <tuple>
#include <iostream>
template <typename ... Ts, std::size_t ... Is>
std::tuple<Ts...> sumT (std::tuple<Ts...> const & t1,
std::tuple<Ts...> const & t2,
std::index_sequence<Is...> const &)
{ return { (std::get<Is>(t1) + std::get<Is>(t2))... }; }
template <typename ... Ts>
std::tuple<Ts...> operator+ (std::tuple<Ts...> const & t1,
std::tuple<Ts...> const & t2)
{ return sumT(t1, t2, std::make_index_sequence<sizeof...(Ts)>{}); }
int main ()
{
std::tuple<int,double> t = std::make_tuple(1,2);
auto t1 = t + t;
std::cout << std::get<0>(t1) << std::endl;
std::cout << std::get<1>(t1) << std::endl;
}
无论如何...我不认为将运算符添加到标准类型是个好主意;也许你只能定义一个 sumT()
函数。
P.s.: std::index_sequence
和 std::make_index_sequence
是 c++14/17 特性;但在 C++11 中模拟它们并不太复杂。
-- 编辑--
OP 问
Thanks a lot, is it possible to make this work for any operators wrappers? Please see the update
我想你的意思如下
#include <tuple>
#include <iostream>
#include <functional>
template <typename Op, typename Tp, std::size_t ... Is>
auto opH2 (Op const & op, Tp const & t1, Tp const & t2,
std::index_sequence<Is...> const &)
{ return std::make_tuple( op(std::get<Is>(t1), std::get<Is>(t2))... ); }
template <typename Op, typename Tp>
auto opH1 (Op const & op, Tp const & t1, Tp const & t2)
{ return opH2(op, t1, t2,
std::make_index_sequence<std::tuple_size<Tp>{}>{}); }
template <typename ... Ts>
auto operator+ (std::tuple<Ts...> const & t1, std::tuple<Ts...> const & t2)
{ return opH1(std::plus<>{}, t1, t2); }
template <typename ... Ts>
auto operator- (std::tuple<Ts...> const & t1, std::tuple<Ts...> const & t2)
{ return opH1(std::minus<>{}, t1, t2); }
template <typename ... Ts>
auto operator* (std::tuple<Ts...> const & t1, std::tuple<Ts...> const & t2)
{ return opH1(std::multiplies<>{}, t1, t2); }
template <typename ... Ts>
auto operator/ (std::tuple<Ts...> const & t1, std::tuple<Ts...> const & t2)
{ return opH1(std::divides<>{}, t1, t2); }
int main ()
{
std::tuple<int,double> t = std::make_tuple(1,2);
auto t1 = t + t;
auto t2 = t - t;
auto t3 = t * t;
auto t4 = t / t;
std::cout << std::get<0>(t1) << ", " << std::get<1>(t1) << std::endl;
std::cout << std::get<0>(t2) << ", " << std::get<1>(t2) << std::endl;
std::cout << std::get<0>(t3) << ", " << std::get<1>(t3) << std::endl;
std::cout << std::get<0>(t4) << ", " << std::get<1>(t4) << std::endl;
}
根据标准,您不能在 namespace std
中执行此操作。将用户编写的代码注入 std
仅在非常有限的情况下才合法,而这不是其中之一。
您可以将它放在全局命名空间中,但是如果在全局命名空间之外,您将无法在没有 using ::operator+;
或类似名称的情况下找到它。即,一个糟糕的计划。
这给您留下了几个选择。您可以实现命名运算符。您可以创建一个标记类型,并声明包含该标记类型的元组参与您的重载决策。您可以创建一个派生自 std::tuple
并具有这些运算符的修改后的元组类型。
我来做第二个。这是有效的,因为运算符的查找遵循类型所在的命名空间,以及您正在处理的模板类型实例的所有模板参数的命名空间。
其他两个选项(派生类型和命名运算符)可以通过类似的实现来完成。这是 c++14,因为它使代码更短。
namespace tuple_operators {
struct enable{};
namespace tuple_operators {
struct enable{};
template<class...Lhs, class...Rhs>
auto operator+( std::tuple<enable, Lhs...> const& lhs, std::tuple<enable, Rhs...> const& rhs ) {
return utility::index_upto<sizeof...(Lhs)>()([&](auto...Is){
using std::get;
return std::make_tuple<
enable,
std::decay_t<
decltype(get<Is+1>(lhs)+get<Is+1>(rhs))
>...
>(
enable{}, (get<Is+1>(lhs)+get<Is+1>(rhs))...
);
});
}
}
其中 index_upto
是:
namespace utility {
template<std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
return [](auto&& f)->decltype(auto) {
return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
};
}
template<std::size_t N>
auto index_upto( std::integral_constant<std::size_t, N> ={} ) {
return index_over( std::make_index_sequence<N>{} );
}
}
这只是一个包扩展的助手,无需在包扩展点编写另一个函数。
我正在尝试编写模板 functions/operators,例如 +
,用于在相同类型的两个元组之间进行算术运算。例如,对于
std::tuple<int,double> t = std::make_tuple(1,2);
我愿意
auto t1 = t + t;
逻辑很简单:按元素进行算术运算。但我无法弄清楚如何在 c++ 模板编程 (c++11/17) 中完成这项工作。我的以下代码无法使用 g++ -std=c++11 tuple_arith.cpp
进行编译。特别是,我想不出让通用 add
函数 (template<typename T> T add(T x, T y) { return x + y; }
) 与元组操作代码一起工作的正确方法。
有人可以帮忙解释一下如何解决这个问题吗?
#include <tuple>
namespace std {
template<typename _Tp, size_t __i, size_t __size, typename _opT >
struct __tuple_arith {
static constexpr _Tp __op(const _Tp& __t, const _Tp& __u, const _opT& op) {
return std::tuple_cat(std::make_tuple(op(std::get<__i>(__t), std::get<__i>(__u))
, __tuple_arith<_Tp, __i + 1, __size, _opT>::__op(__t, __u)));
}
};
template<typename _Tp, size_t __size, typename _opT>
struct __tuple_arith<_Tp, __size, __size - 1, _opT> {
static constexpr _Tp __op(const _Tp& __t, const _Tp& __u, const _opT& op) {
return std::make_tuple(op(std::get<__size-1>(__t), std::get<__size -1>(__u)));
}
};
template<typename T> T add(T x, T y) { return x + y; }
template<typename... _TElements> constexpr tuple<_TElements...>
operator+(const tuple<_TElements...>& __t, const tuple<_TElements...>& __u) {
using op = __tuple_arith<tuple<_TElements...>, 0, sizeof...(_TElements), decltype(add)>;
return op::__op(__t, __u, add);
}
}; //namespace std
#include <iostream>
using namespace std;
int main() {
std::tuple<int,double> t = std::make_tuple(1,2);
auto t1 = t + t;
cout << std::get<0>(t1) << std::endl;
return 0;
}
具体错误为:
tuple_arith.cpp:14:10: error: template argument ‘(__size - 1)’ involves template parameter(s)
struct __tuple_arith<_Tp, __size, __size - 1, _opT> {
^
tuple_arith.cpp: In function ‘constexpr std::tuple<_Elements ...> std::operator+(const std::tuple<_Elements ...>&, const std::tuple<_Elements ...>&)’:
tuple_arith.cpp:24:90: error: decltype cannot resolve address of overloaded function
__tuple_arith<tuple<_TElements...>, 0, sizeof...(_TElements), decltype(add)>;
^
tuple_arith.cpp:24:91: error: template argument 4 is invalid
__tuple_arith<tuple<_TElements...>, 0, sizeof...(_TElements), decltype(add)>;
^
tuple_arith.cpp:25:12: error: ‘op’ has not been declared
return op::__op(__t, __u, add);
^
tuple_arith.cpp: In instantiation of ‘constexpr std::tuple<_Elements ...> std::operator+(const std::tuple<_Elements ...>&, const std::tuple<_Elements ...>&) [with _TElements = {int, double}]’:
tuple_arith.cpp:34:17: required from here
tuple_arith.cpp:26:3: error: body of constexpr function ‘constexpr std::tuple<_Elements ...> std::operator+(const std::tuple<_Elements ...>&, const std::tuple<_Elements ...>&) [with _TElements = {int, double}]’ not a return-statement
}
^
--更新--
感谢您到目前为止提供的有用答案。是否有可能使其适用于任何 Operator Wrappers,例如std::{plus,minus,multiplies,divides}
?这就是我试图通过模板参数 typename _opT
实现的目标。最后,我正在寻找一个可以将兼容的运算符作为参数的function/object。
您的代码中的问题是您不能基于另一个模板值部分特化一个模板值;你可以解决这个问题,但是......为什么?
用std::index_sequence
#include <tuple>
#include <iostream>
template <typename ... Ts, std::size_t ... Is>
std::tuple<Ts...> sumT (std::tuple<Ts...> const & t1,
std::tuple<Ts...> const & t2,
std::index_sequence<Is...> const &)
{ return { (std::get<Is>(t1) + std::get<Is>(t2))... }; }
template <typename ... Ts>
std::tuple<Ts...> operator+ (std::tuple<Ts...> const & t1,
std::tuple<Ts...> const & t2)
{ return sumT(t1, t2, std::make_index_sequence<sizeof...(Ts)>{}); }
int main ()
{
std::tuple<int,double> t = std::make_tuple(1,2);
auto t1 = t + t;
std::cout << std::get<0>(t1) << std::endl;
std::cout << std::get<1>(t1) << std::endl;
}
无论如何...我不认为将运算符添加到标准类型是个好主意;也许你只能定义一个 sumT()
函数。
P.s.: std::index_sequence
和 std::make_index_sequence
是 c++14/17 特性;但在 C++11 中模拟它们并不太复杂。
-- 编辑--
OP 问
Thanks a lot, is it possible to make this work for any operators wrappers? Please see the update
我想你的意思如下
#include <tuple>
#include <iostream>
#include <functional>
template <typename Op, typename Tp, std::size_t ... Is>
auto opH2 (Op const & op, Tp const & t1, Tp const & t2,
std::index_sequence<Is...> const &)
{ return std::make_tuple( op(std::get<Is>(t1), std::get<Is>(t2))... ); }
template <typename Op, typename Tp>
auto opH1 (Op const & op, Tp const & t1, Tp const & t2)
{ return opH2(op, t1, t2,
std::make_index_sequence<std::tuple_size<Tp>{}>{}); }
template <typename ... Ts>
auto operator+ (std::tuple<Ts...> const & t1, std::tuple<Ts...> const & t2)
{ return opH1(std::plus<>{}, t1, t2); }
template <typename ... Ts>
auto operator- (std::tuple<Ts...> const & t1, std::tuple<Ts...> const & t2)
{ return opH1(std::minus<>{}, t1, t2); }
template <typename ... Ts>
auto operator* (std::tuple<Ts...> const & t1, std::tuple<Ts...> const & t2)
{ return opH1(std::multiplies<>{}, t1, t2); }
template <typename ... Ts>
auto operator/ (std::tuple<Ts...> const & t1, std::tuple<Ts...> const & t2)
{ return opH1(std::divides<>{}, t1, t2); }
int main ()
{
std::tuple<int,double> t = std::make_tuple(1,2);
auto t1 = t + t;
auto t2 = t - t;
auto t3 = t * t;
auto t4 = t / t;
std::cout << std::get<0>(t1) << ", " << std::get<1>(t1) << std::endl;
std::cout << std::get<0>(t2) << ", " << std::get<1>(t2) << std::endl;
std::cout << std::get<0>(t3) << ", " << std::get<1>(t3) << std::endl;
std::cout << std::get<0>(t4) << ", " << std::get<1>(t4) << std::endl;
}
根据标准,您不能在 namespace std
中执行此操作。将用户编写的代码注入 std
仅在非常有限的情况下才合法,而这不是其中之一。
您可以将它放在全局命名空间中,但是如果在全局命名空间之外,您将无法在没有 using ::operator+;
或类似名称的情况下找到它。即,一个糟糕的计划。
这给您留下了几个选择。您可以实现命名运算符。您可以创建一个标记类型,并声明包含该标记类型的元组参与您的重载决策。您可以创建一个派生自 std::tuple
并具有这些运算符的修改后的元组类型。
我来做第二个。这是有效的,因为运算符的查找遵循类型所在的命名空间,以及您正在处理的模板类型实例的所有模板参数的命名空间。
其他两个选项(派生类型和命名运算符)可以通过类似的实现来完成。这是 c++14,因为它使代码更短。
namespace tuple_operators {
struct enable{};
namespace tuple_operators {
struct enable{};
template<class...Lhs, class...Rhs>
auto operator+( std::tuple<enable, Lhs...> const& lhs, std::tuple<enable, Rhs...> const& rhs ) {
return utility::index_upto<sizeof...(Lhs)>()([&](auto...Is){
using std::get;
return std::make_tuple<
enable,
std::decay_t<
decltype(get<Is+1>(lhs)+get<Is+1>(rhs))
>...
>(
enable{}, (get<Is+1>(lhs)+get<Is+1>(rhs))...
);
});
}
}
其中 index_upto
是:
namespace utility {
template<std::size_t...Is>
auto index_over( std::index_sequence<Is...> ) {
return [](auto&& f)->decltype(auto) {
return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
};
}
template<std::size_t N>
auto index_upto( std::integral_constant<std::size_t, N> ={} ) {
return index_over( std::make_index_sequence<N>{} );
}
}
这只是一个包扩展的助手,无需在包扩展点编写另一个函数。