从标准容器生成 std::tuple
Generate a std::tuple from standard container
是否有一种可移植的方法从容器的内容生成 std::tuple
(实际上是 std::array
)?这样的元组将允许 std::apply
从容器中提取函数参数。
我第一次尝试使用尾递归,但因编译器错误而失败:"recursive template instantiation exceeds maximum...".
我无法完成第二次尝试(std::for_each
使用可变 lambda 保存元组)来编译所需的结果。
我假设类似于 boost::mpl
处理可变元函数的方法(即使用 boost::preprocessor
的魔法)可以起作用——但 c++03 就是这样。我希望有更好的解决方案。
函数签名类似于:
std::list<int> args_as_list = {1, 2, 3, 4};
auto tpl = args_as_tuple(args_as_list);
其中 tpl
的类型是 std::array<int const, 4>
。
简答:否,不可能。
说明:std::tuple
和std::array
都需要编译时关于元素数量的信息。 std::list
或 std::vector
只能提供有关元素计数的 运行时 信息。
您的 args_as_tuple
函数必须是模板,将预期参数的数量作为模板参数 (args_as_tuple<4>(args_as_list)
)。
虽然必须将参数数量作为模板参数似乎很苛刻,但在您的示例中,这是非常明显的 - 必须在编译时知道函数参数数量(提供给 std::apply
的函数)-还有时间。
对于更通用的代码,您可以使用:function-traits or code from this answer.
或者使用 std::array
而不是 std::list
(很多通用模板代码,但编译时检查很好)
std::tuple
或 std::array
中的元素数量是其类型信息的一部分。因此,您上面提出的函数 args_as_tuple
必须以某种方式成为模板,并且结果的每种不同可能大小都需要对该模板进行不同的实例化。因此,除非该程序的代码是无限的(不可能),否则您无法编写可以支持任意多种大小的元组的程序。
如果您只关心 int
的取值范围,比如说,您可以将模板实例化 40 亿次,但是您的可执行文件将至少有 4 GB 大。
如果您真的只关心实际程序中几个 不同大小的向量,您可以只实例化那些模板并编写转换代码,以计算 std::list::size()
并调用适当的函数(乏味)。
但是你的确切代码片段
std::list<int> args_as_list = {1, 2, 3, 4};
auto tpl = args_as_tuple(args_as_list);
在 C++ 中永远无法工作。因为,在 C++ 中,所有变量都必须具有在编译时确定的已知类型。即使您使用关键字 auto
,auto
也必须在编译时解析为固定类型,这意味着如果它是元组或数组则固定大小,无论哪种模板恶作剧表达式 args_as_tuple
正在做。
由于我的问题无法解决,我解决了一个略有不同的问题,这让我继续前进。
我想出了一个解决方案,它允许我从容器中提取仿函数的参数。我可以用我想要评估的仿函数实例化一个 eval_container
,然后将容器传递给结果对象。
#include <utility>
template <int N>
using Int = std::integral_constant<int, N>;
template <typename T>
struct arity : arity<decltype(&T::operator())> {};
template <typename T, typename RT, typename...Args>
struct arity<RT(T::*)(Args...) const>
{
// could enforce maximum number of arguments
static constexpr int value = sizeof...(Args);
};
template <typename F, int N = arity<F>::value>
struct eval_container
{
eval_container(F const& f) : f(f) {}
eval_container(F&& f) : f(std::move(f)) {}
template <typename Iter, typename I, typename...Args>
auto operator()(Iter&& iter, I, Args&&...args) const
{
// assert(iter != end)
auto&& arg = *iter++;
return (*this)(std::forward<Iter>(iter)
, Int<I()-1>{}
, std::forward<Args>(args)...
, arg);
}
template <typename Iter, typename...Args>
auto operator()(Iter&&, Int<0>, Args&&...args) const
{
// assert(iter == end)
return f(std::forward<Args>(args)...);
}
template <typename C>
auto operator()(C const& container) const
{
return (*this)(container.begin(), Int<N>{});
}
F f;
};
}
是否有一种可移植的方法从容器的内容生成 std::tuple
(实际上是 std::array
)?这样的元组将允许 std::apply
从容器中提取函数参数。
我第一次尝试使用尾递归,但因编译器错误而失败:"recursive template instantiation exceeds maximum...".
我无法完成第二次尝试(std::for_each
使用可变 lambda 保存元组)来编译所需的结果。
我假设类似于 boost::mpl
处理可变元函数的方法(即使用 boost::preprocessor
的魔法)可以起作用——但 c++03 就是这样。我希望有更好的解决方案。
函数签名类似于:
std::list<int> args_as_list = {1, 2, 3, 4};
auto tpl = args_as_tuple(args_as_list);
其中 tpl
的类型是 std::array<int const, 4>
。
简答:否,不可能。
说明:std::tuple
和std::array
都需要编译时关于元素数量的信息。 std::list
或 std::vector
只能提供有关元素计数的 运行时 信息。
您的 args_as_tuple
函数必须是模板,将预期参数的数量作为模板参数 (args_as_tuple<4>(args_as_list)
)。
虽然必须将参数数量作为模板参数似乎很苛刻,但在您的示例中,这是非常明显的 - 必须在编译时知道函数参数数量(提供给 std::apply
的函数)-还有时间。
对于更通用的代码,您可以使用:function-traits or code from this answer.
或者使用 std::array
而不是 std::list
(很多通用模板代码,但编译时检查很好)
std::tuple
或 std::array
中的元素数量是其类型信息的一部分。因此,您上面提出的函数 args_as_tuple
必须以某种方式成为模板,并且结果的每种不同可能大小都需要对该模板进行不同的实例化。因此,除非该程序的代码是无限的(不可能),否则您无法编写可以支持任意多种大小的元组的程序。
如果您只关心 int
的取值范围,比如说,您可以将模板实例化 40 亿次,但是您的可执行文件将至少有 4 GB 大。
如果您真的只关心实际程序中几个 不同大小的向量,您可以只实例化那些模板并编写转换代码,以计算 std::list::size()
并调用适当的函数(乏味)。
但是你的确切代码片段
std::list<int> args_as_list = {1, 2, 3, 4};
auto tpl = args_as_tuple(args_as_list);
在 C++ 中永远无法工作。因为,在 C++ 中,所有变量都必须具有在编译时确定的已知类型。即使您使用关键字 auto
,auto
也必须在编译时解析为固定类型,这意味着如果它是元组或数组则固定大小,无论哪种模板恶作剧表达式 args_as_tuple
正在做。
由于我的问题无法解决,我解决了一个略有不同的问题,这让我继续前进。
我想出了一个解决方案,它允许我从容器中提取仿函数的参数。我可以用我想要评估的仿函数实例化一个 eval_container
,然后将容器传递给结果对象。
#include <utility>
template <int N>
using Int = std::integral_constant<int, N>;
template <typename T>
struct arity : arity<decltype(&T::operator())> {};
template <typename T, typename RT, typename...Args>
struct arity<RT(T::*)(Args...) const>
{
// could enforce maximum number of arguments
static constexpr int value = sizeof...(Args);
};
template <typename F, int N = arity<F>::value>
struct eval_container
{
eval_container(F const& f) : f(f) {}
eval_container(F&& f) : f(std::move(f)) {}
template <typename Iter, typename I, typename...Args>
auto operator()(Iter&& iter, I, Args&&...args) const
{
// assert(iter != end)
auto&& arg = *iter++;
return (*this)(std::forward<Iter>(iter)
, Int<I()-1>{}
, std::forward<Args>(args)...
, arg);
}
template <typename Iter, typename...Args>
auto operator()(Iter&&, Int<0>, Args&&...args) const
{
// assert(iter == end)
return f(std::forward<Args>(args)...);
}
template <typename C>
auto operator()(C const& container) const
{
return (*this)(container.begin(), Int<N>{});
}
F f;
};
}