decltype用于递归变元函数模板的返回类型
decltype for the return type of recursive variadic function template
给定以下代码(取自此处):
#include <cstddef>
#include <type_traits>
#include <tuple>
#include <iostream>
#include <utility>
#include <functional>
template<typename ... Fs>
struct compose_impl
{
compose_impl(Fs&& ... fs) : functionTuple(std::forward_as_tuple(fs ...)) {}
template<size_t N, typename ... Ts>
auto apply(std::integral_constant<size_t, N>, Ts&& ... ts) const
{
return apply(std::integral_constant<size_t, N - 1>(), std::get<N> (functionTuple)(std::forward<Ts>(ts)...));
}
template<typename ... Ts>
auto apply(std::integral_constant<size_t, 0>, Ts&& ... ts) const
{
return std::get<0>(functionTuple)(std::forward<Ts>(ts)...);
}
template<typename ... Ts>
auto operator()(Ts&& ... ts) const
{
return apply(std::integral_constant<size_t, sizeof ... (Fs) - 1>(), std::forward<Ts>(ts)...);
}
std::tuple<Fs ...> functionTuple;
};
template<typename ... Fs>
auto compose(Fs&& ... fs)
{
return compose_impl<Fs ...>(std::forward<Fs>(fs) ...);
}
int main ()
{
auto f1 = [](std::pair<double,double> p) {return p.first + p.second; };
auto f2 = [](double x) {return std::make_pair(x, x + 1.0); };
auto f3 = [](double x, double y) {return x*y; };
auto g = compose(f1, f2, f3);
std::cout << g(2.0, 3.0) << std::endl; //prints '13', evaluated as (2*3) + ((2*3)+1)
return 0;
}
上面的代码在C++14中工作。我在让它为C++11工作时遇到了一些问题。我试图为所涉及的函数模板正确地提供返回类型,但没有取得多大成功,例如:
template<typename... Fs>
struct compose_impl
{
compose_impl(Fs&&... fs) : func_tup(std::forward_as_tuple(fs...)) {}
template<size_t N, typename... Ts>
auto apply(std::integral_constant<size_t, N>, Ts&&... ts) const -> decltype(std::declval<typename std::tuple_element<N, std::tuple<Fs...>>::type>()(std::forward<Ts>(ts)...))
// -- option 2. decltype(apply(std::integral_constant<size_t, N - 1>(), std::declval<typename std::tuple_element<N, std::tuple<Fs...>>::type>()(std::forward<Ts>(ts)...)))
{
return apply(std::integral_constant<size_t, N - 1>(), std::get<N>(func_tup)(std::forward<Ts>(ts)...));
}
using func_type = typename std::tuple_element<0, std::tuple<Fs...>>::type;
template<typename... Ts>
auto apply(std::integral_constant<size_t, 0>, Ts&&... ts) const -> decltype(std::declval<func_type>()(std::forward<Ts>(ts)...))
{
return std::get<0>(func_tup)(std::forward<Ts>(ts)...);
}
template<typename... Ts>
auto operator()(Ts&&... ts) const -> decltype(std::declval<func_type>()(std::forward<Ts>(ts)...))
// -- option 2. decltype(apply(std::integral_constant<size_t, sizeof...(Fs) - 1>(), std::forward<Ts>(ts)...))
{
return apply(std::integral_constant<size_t, sizeof...(Fs) - 1>(), std::forward<Ts>(ts)...);
}
std::tuple<Fs...> func_tup;
};
template<typename... Fs>
auto compose(Fs&&... fs) -> decltype(compose_impl<Fs...>(std::forward<Fs>(fs)...))
{
return compose_impl<Fs...>(std::forward<Fs>(fs)...);
}
对于上面的clang(3.5.0)给了我以下错误:
func_compose.cpp:79:18: error: no matching function for call to object of type 'compose_impl<(lambda at func_compose.cpp:65:15) &, (lambda at func_compose.cpp:67:15) &,
(lambda at func_compose.cpp:68:15) &>'
std::cout << g(2.0, 3.0) << std::endl; //prints '13', evaluated as (2*3) + ((2*3)+1)
^
func_compose.cpp:31:10: note: candidate template ignored: substitution failure [with Ts = <double, double>]: no matching function for call to object of type
'(lambda at func_compose.cpp:65:15)'
auto operator()(Ts&&... ts) /*const*/ -> decltype(std::declval<func_type>()(std::forward<Ts>(ts)...))
^ ~~~
1 error generated.
如果我尝试"选项2",我会得到几乎相同的错误。
除了它看起来很冗长之外,我似乎也不能把它说对。有人能提供一些关于我做错了什么的见解吗?有没有更简单的方法来提供返回类型?
第一个选项的错误消息是由于中
std::declval<func_type>()(std::forward<Ts>(ts)...)
您试图用两个类型为double
的参数(传递给operator()
的参数)调用f1
函子,但它需要一个std::pair
(func_type
指元组中第一个函子的类型)。
关于选项2,它不编译的原因是尾部返回类型是函数声明符的一部分,并且在看到声明符的末尾之前,函数不会被视为已声明,因此不能在apply
的第一个声明的尾部返回类型中使用decltype(apply(...))
。
我相信你现在很高兴知道为什么你的代码没有编译,但我想如果你有一个有效的解决方案,你会更高兴。
我认为有一个重要的事实需要首先澄清:compose_impl
中apply
和operator()
模板的所有特殊化都有相同的返回类型——在这种情况下是第一个函子f1
的返回类型。
有几种方法可以获得这种类型,但快速破解如下:
#include <cstddef>
#include <type_traits>
#include <tuple>
#include <iostream>
#include <utility>
#include <functional>
template<typename> struct ret_hlp;
template<typename F, typename R, typename... Args> struct ret_hlp<R (F::*)(Args...) const>
{
using type = R;
};
template<typename F, typename R, typename... Args> struct ret_hlp<R (F::*)(Args...)>
{
using type = R;
};
template<typename ... Fs>
struct compose_impl
{
compose_impl(Fs&& ... fs) : functionTuple(std::forward_as_tuple(fs ...)) {}
using f1_type = typename std::remove_reference<typename std::tuple_element<0, std::tuple<Fs...>>::type>::type;
using ret_type = typename ret_hlp<decltype(&f1_type::operator())>::type;
template<size_t N, typename ... Ts>
ret_type apply(std::integral_constant<size_t, N>, Ts&& ... ts) const
{
return apply(std::integral_constant<size_t, N - 1>(), std::get<N> (functionTuple)(std::forward<Ts>(ts)...));
}
template<typename ... Ts>
ret_type apply(std::integral_constant<size_t, 0>, Ts&& ... ts) const
{
return std::get<0>(functionTuple)(std::forward<Ts>(ts)...);
}
template<typename ... Ts>
ret_type operator()(Ts&& ... ts) const
{
return apply(std::integral_constant<size_t, sizeof ... (Fs) - 1>(), std::forward<Ts>(ts)...);
}
std::tuple<Fs ...> functionTuple;
};
template<typename ... Fs>
compose_impl<Fs ...> compose(Fs&& ... fs)
{
return compose_impl<Fs ...>(std::forward<Fs>(fs) ...);
}
int main ()
{
auto f1 = [](std::pair<double,double> p) {return p.first + p.second; };
auto f2 = [](double x) {return std::make_pair(x, x + 1.0); };
auto f3 = [](double x, double y) {return x*y; };
auto g = compose(f1, f2, f3);
std::cout << g(2.0, 3.0) << std::endl; //prints '13', evaluated as (2*3) + ((2*3)+1)
return 0;
}
注:
- 它以C++11模式在GCC 4.9.1和Clang 3.5.0上以及在Visual C++2013上编译和工作
- 正如所写的,
ret_hlp
只处理声明其operator()
的函数对象类型,类似于lambda闭包类型,但它可以很容易地扩展到几乎任何其他类型,包括普通函数类型 - 我试图尽可能少地更改原始代码;我认为关于该代码有一个重要的地方需要提及:如果
compose
被赋予了左值参数(如本例所示),compose_impl
中的functionTuple
将存储对这些参数的引用。这意味着只要使用复合函子,原始函子就必须可用,否则就会有悬空引用
编辑:以下是评论中要求的关于最后一条注释的更多信息:
这种行为是由于转发引用的工作方式——compose
的Fs&& ...
函数参数。如果您有一个形式为F&&
的函数参数,正在对其进行模板参数推导(如这里所示),并且为该参数提供了一个类型为A
的参数,那么:
- 如果参数表达式是右值,则
F
被推导为A
,并且当被替换回函数参数时,它给出A&&
(例如,如果将lambda表达式直接作为参数传递给compose
,则会发生这种情况) - 如果自变量表达式是左值,则
F
被推导为A&
,并且当代入函数参数时,它给出A& &&
,根据引用折叠规则产生A&
(这就是当前示例中发生的情况,因为f1
和其他都是左值)
因此,在当前示例中,compose_impl
将使用推导出的模板参数进行实例化,类似于(为lambda闭包类型使用虚构的名称)
compose_impl<lambda_1_type&, lambda_2_type&, lambda_3_type&>
从而使CCD_ 31具有类型
std::tuple<lambda_1_type&, lambda_2_type&, lambda_3_type&>
如果您将lambda表达式作为参数直接传递给compose
,那么根据上面的内容,functionTuple
将具有类型
std::tuple<lambda_1_type, lambda_2_type, lambda_3_type>
因此,只有在后一种情况下,元组才会存储函数对象的副本,从而使组合的函数对象类型成为自包含的。
现在,问题不在于这是好是坏;这是一个你想要什么的问题。
如果您希望组合的对象始终是自包含的(存储函子的副本),那么您需要去掉这些引用。这里的一种方法是使用std::decay
,因为它不仅可以删除引用,还可以处理函数到指针的转换,如果您想将compose_impl
扩展为能够处理普通函数,这将非常有用。
最简单的方法是更改functionTuple
的声明,因为它是当前实现中唯一关心引用的地方:
std::tuple<typename std::decay<Fs>::type ...> functionTuple;
结果是,函数对象将始终在元组中复制或移动,因此即使在原始组件被破坏后,也可以使用所生成的组合函数对象。
哇,时间变长了;也许你不应该说"精心设计":-)。
编辑OP的第二条注释2:是的,没有std::decay
的代码(但正如您所说,扩展为正确确定普通函数参数的ret_type
)将处理普通函数,但要小心:
int f(int) { return 7; }
int main()
{
auto c1 = compose(&f, &f); //Stores pointers to function f.
auto c2 = compose(f, f); //Stores references to function f.
auto pf = f; //pf has type int(*)(int), but is an lvalue, as opposed to &f, which is an rvalue.
auto c3 = compose(pf, pf); //Stores references to pointer pf.
std::cout << std::is_same<decltype(c1.functionTuple), std::tuple<int(*)(int), int(*)(int)>>::value << 'n';
std::cout << std::is_same<decltype(c2.functionTuple), std::tuple<int(&)(int), int(&)(int)>>::value << 'n';
std::cout << std::is_same<decltype(c3.functionTuple), std::tuple<int(*&)(int), int(*&)(int)>>::value << 'n';
}
c3
的行为可能不是你想要的,也不是人们所期望的。更不用说所有这些变体可能会混淆用于确定ret_type
的代码。
有了std::decay
,所有三个变体都存储指向函数f
的指针。
- 为什么 c++(g++) 不允许模板返回类型和函数名称之间有空格?
- 函数模板返回类型
- 在模板化成员函数的返回类型中使用 std::enable_if 时的编译器差异
- 我可以推导作为模板Agument传递的函数的返回类型吗
- 使用函数模板返回类型 X 的变量的值
- 返回函数模板的类型C++作为第二个模板参数
- 模板返回类型函数如何在C++中工作
- 模板函数的返回类型未知,使用 decltype 时代码重复
- 确定模板函数的返回类型
- 如何检索要在模板中使用的函数的返回类型
- 当我使用模板时,编译器无法将函数的返回类型从 double 转换为 int
- 模板友元函数和返回类型推导
- 如何在模板中推断函数的返回类型
- 推断CRTP中模板化成员函数的返回类型
- std::函数,返回类型为void,参数为模板化
- 使用类外模板类的typedef成员作为成员函数的返回类型
- 如何使c++模板化函数与返回类型无关,以便将来专门化
- 我可以避免返回函数的模板返回类型吗?
- 是否可以更改专门化模板函数的返回类型?
- 我可以默认这个Transform模板函数的返回类型吗?