从其迭代器推断参数类型
deduce argument type from its iterator
我正在尝试在C++中实现Haskell函数foldr
:
template <typename F, typename ForwardIter, typename B>
B foldr(F f, B z0, ForwardIter begin, ForwardIter end)
{
if (begin == end) {
return z0;
} else {
auto lval = *begin;
return f(lval, foldr(f, z0, ++begin, end));
}
}
我的问题是。我可以使用ForwardIter
的信息推断出要std::function
的类型F
吗?虚数类型:F = std::function<B(typename ForwardIter::value, B)>
考虑到指示参数之间关系的 Haskell 签名(a->b->b)->b->[a]->b
,我想知道是否有一种方法可以在C++中指定这种关系。因此,当我偶然将错误的函子传递给第一个参数时,编译器会告诉我她需要一个具有精确签名的函子。
考虑到指示参数之间关系的 Haskell 签名
(a->b->b)->b->[a]->b
,我想知道是否有一种方法可以在C++中指定这种关系。
是的,有。不过,你误以为一个人用std::function
来做这件事是错误的。 std::function
是具有已知签名的可调用对象的容器。为什么人们会把东西包装在一个容器里,只是为了把它作为一个参数传递?
您可以简单地使用尾随返回类型,例如:
template<typename F,
typename ForwardIter,
typename B,
typename ValueType = typename std::iterator_traits<ForwardIter>::value_type>
auto foldr(F f, B z0, ForwardIter begin, ForwardIter end)
-> decltype(std::declval<F&>()(std::declval<ValueType&>(), std::declval<B&>())) { ...
您还可以根据您的需求定义一个特征(即 F
可以使用迭代器的value_type
参数和另一个类型为 B
的参数进行调用,并返回 B
(,并使用该特征拒绝其他可调用对象。
template<typename F,
typename ForwardIter,
typename B>
B foldr(F f, B z0, ForwardIter begin, ForwardIter end)
{
using ValueType = typename std::iterator_traits<ForwardIter>::value_type;
static_assert(is_callable<F&, B(ValueType&, B&)>(), "F must be callable with signature B(B, B)");
...
如果对象无法使用正确的签名调用,这将导致错误。如果只想从过载解析中排除重载,则可以将 SFINAE 与 enable_if
template<typename F,
typename ForwardIter,
typename B,
typename ValueType = typename std::iterator_traits<ForwardIter>::value_type,
typename std::enable_if<is_callable<F&, B(ValueType&,B&)>::value, int>::type = 0>
B foldr(F f, B z0, ForwardIter begin, ForwardIter end) { ...
或者,您可以使用该特征进行标记调度。或者别的什么。重要的一点是,这不是std::function
的目的(这不仅仅是运行时开销的问题;它还使某些代码无法编译,而如果没有std::function
痴迷,它可以正常工作(。
综上所述,如果我正在编写这个函数,我或多或少会像这样开始(注意所有的引用语义和转发(:
template<typename F,
typename It,
typename B,
typename Reference = typename std::iterator_traits<It>::reference,
typename std::enable_if<is_callable<F&, B(Reference&&,B)>::value, int>::type = 0>
B foldr(F&& f, B&& z0, It first, It last)
{
if (first == end) {
return std::forward<B>(z0);
} else {
auto&& r = *first; // must do here to avoid order of evaluation woes
// don't forward f because we cannot "destroy" it twice, i.e. no moving!
return f(std::forward<decltype(r)>(r), foldr(f, std::forward<B>(z0), ++first, end));
}
}
使用 decltype
关键字。 在代码中使用auto
表明您使用的是 C++11。 在函数的顶部:
typedef decltype(*begin) ForwardIterValue; // type of result of applying dereference operator
typedef std::function< B(ForwardIterValue, B) > F; // desired function object
正如维基百科条目所说,
它的主要预期用途是在泛型编程中,它通常是 难以,甚至不可能表达依赖于的类型 模板参数
根据构造函数对象的方式,也可以为此使用 auto
。
是的,这是可能的。优点是,如果您使用许多不同的函数重复调用foldr
,但ForwardIterator
和B
的类型相同,您将只会获得生成的代码的一个版本,因此(可能(是一个较小的可执行文件。
然而,我的直觉说"不要这样做"。正是由于std::function
的泛型属性,编译器几乎没有机会内联你对f()
的调用;反过来,它可能难以展开对foldr
的递归调用。另一方面,如果你允许它为每个函数合成一个专门的foldr
,你就有更好的机会获得高度优化的实现。如果f
是简单的东西,比如std::plus
或类似的东西,则尤其如此。
例如,在 -03 中使用 Clang 3.1 尝试以下操作:
std::vector<int> v{1, 2, 3, 4};
std::cout << foldr(std::plus{}, 5, v.begin(), v.end()) << std::endl;
导致对foldr
的呼吁被完全消除。使用 std::function
的相同代码的版本具有更多的开销。
当然,本能和直觉是决定是否做某事的特别可怕的方式,当C++模板参与其中时,这种情况会加倍。我建议尝试这两种方法,看看哪种方法更好。
typename std::iterator_traits<ForwardIter>::value_type
- 迭代器具有成员
typedef ... value_type;
,或 - 存在适当的
iterator_traits
专业化(例如指针(。
- 扩展C++生成的代码的模板参数类型名称
- 如何在 c++ 中定义接受不同参数类型的函数向量?
- 在 C++ 中运行时调用模板时,是否可以切换模板的参数类型?
- 将函数参数类型声明为 auto
- 将函数的参数 - 签名从使用 'std::function<T>' 转换为模板参数类型
- 在 C++17 中调用具有不同参数类型的构造函数
- 具有先前参数类型匹配的参数包
- 我想知道为什么"std::unique_ptr<int> foo(新 int)"是合法的,因为"std::<int>unique_ptr"要求输入参数类型应该是"int"?
- 将可变参数类型列表的扩展打包为复杂类型的初始值设定项列表 - 合法吗?
- MSVC 错误:4 个重载中的任何一个都无法转换所有参数类型
- 使用constexpr + auto作为返回和参数类型的奇怪类型推导
- 如何从第一个参数推断第二个参数类型?
- C++模板函数中,指定回调函子/lambda 的参数类型,同时仍允许内联?
- 如何用不同的参数类型和数字回调函数
- C++stoi:这两个重载都无法转换所有参数类型
- 为什么std::{container}::template不能推导其参数类型
- 为模板参数类型中的新对象分配内存
- 为指向成员模板参数的指针推导额外模板参数类型的紧凑方式
- 使用std::conditional中的模板来确定函数参数类型
- C++中的短参数类型