decltype为递归类型的递归可变参数函数模板

时间:2015-02-17 21:58:21

标签: c++ c++11 variadic-templates c++14 decltype

给出以下代码(取自here):

#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”。我得到了几乎相同的错误。

除了它看起来非常冗长之外,我似乎也无法做到正确。谁能提供一些有关我做错的见解? 有没有更简单的方法来提供返回类型?

1 个答案:

答案 0 :(得分:5)

第一个选项的错误消息是由

中的事实引起的
std::declval<func_type>()(std::forward<Ts>(ts)...)

您尝试使用f1类型的两个参数(传递给double的参数)调用operator()仿函数,但需要std::pair({{ 1}}指的是元组中第一个仿函数的类型。

关于选项2,它不编译的原因是尾随返回类型是函数声明符的一部分,并且在看到声明符的结尾之前不会将该函数视为声明,因此您不能使用{第一个func_type声明的尾随返回类型中的{1}}。


我相信你现在非常高兴知道为什么你的代码无法编译,但我想如果你有一个可行的解决方案,你会更高兴。

我认为有一个重要的事实需要先澄清:decltype(apply(...)) applyapply模板的所有专精都具有相同的返回类型 - 第一个仿函数的返回类型,在这种情况下为operator()

有几种方法可以获得该类型,但快速破解如下:

compose_impl

注意:

  • 它在C ++ 11模式和Visual C ++ 2013上编译和使用GCC 4.9.1和Clang 3.5.0。
  • 如上所述,f1仅处理与lambda闭包类型类似地声明其#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; } 的函数对象类型,但它可以很容易地扩展到其他任何东西,包括普通函数类型。
  • 我试图尽可能少地更改原始代码;我认为关于该代码需要提及一个重要的一点:如果给ret_hlp赋予左值参数(如本例所示),operator()内的compose将存储引用< / em>对那些论点。这意味着只要使用复合仿​​函数,原始仿函数就必须可用,否则你将有悬空参考。

编辑:以下是评论中要求的最后一个注释的更多信息:

这种行为是由于转发引用的工作方式 - functionTuple的{​​{1}}函数参数。如果你有一个compose_impl形式的函数参数,正在进行模板参数推导(就像这里一样),并且为该参数给出了Fs&& ...类型的参数,那么:

  • 如果参数表达式是 rvalue ,则compose推导为F&&,并且当替换回函数参数时,它会给出A(例如,如果您将lambda表达式直接作为参数传递给F),就会发生这种情况;
  • 如果参数表达式是左值,则A推导为A&&,并且当替换回函数参数时,它会给出compose,根据{{​​3}}规则产生F(这是当前示例中发生的情况,A&,其他是左值。)

因此,在当前示例中,A& &&将使用推导出的模板参数进行实例化(类似于使用发明名称的lambda闭包类型)

A&

反过来会使f1具有类型

compose_impl

如果你将lambda表达式作为参数直接传递给compose_impl<lambda_1_type&, lambda_2_type&, lambda_3_type&> ,那么,根据上面的说明,functionTuple将具有类型

std::tuple<lambda_1_type&, lambda_2_type&, lambda_3_type&>

因此,只有在后一种情况下,元组才会存储函数对象的副本,使得组合函数对象类型自包含。

现在,这不是一个好或坏的问题;这是你想要的问题。

如果您希望组合对象始终是自包含的(存储仿函数的副本),那么您需要摆脱这些引用。这里做的一种方法是使用reference collapsing,因为它不仅仅是删除引用 - 它还处理函数到指针的转换,如果你想扩展compose就能派上用场也处理普通功能。

最简单的方法是更改​​functionTuple的声明,因为它是您关注当前实现中引用的唯一地方:

std::tuple<lambda_1_type, lambda_2_type, lambda_3_type>

结果是函数对象将始终在元组内复制或移动,因此即使在原始组件被破坏后也可以使用生成的组合函数对象。

哇,这很久了;也许你不应该说'精心': - )。


编辑2来自OP的第二条评论:是的,代码不是compose_impl(但扩展为正确确定functionTuple的普通函数参数,如你所说)将处理简单的功能,但要小心:

std::tuple<typename std::decay<Fs>::type ...> functionTuple;

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的指针。