jjzjj

c++ - 递归可变参数函数模板的返回类型的decltype

coder 2024-02-18 原文

给定以下代码(取自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”。我得到几乎相同的错误。

除了看起来很冗长之外,我似乎也无法做到这一点。谁能提供一些我做错了什么的见解?
有没有更简单的方法来提供返回类型?

最佳答案

您的第一个选择的错误消息是由于以下事实

std::declval<func_type>()(std::forward<Ts>(ts)...)

您正在尝试使用两个类型为f1的参数(传递给double的参数)调用operator()仿函数,但它需要一个std::pair(func_type表示元组中第一个仿函数的类型)。

关于选项2,之所以无法编译,是因为尾随返回类型是函数声明器的一部分,并且直到看到该声明器的末尾,该函数才被认为是声明了,因此您不能在尾部使用decltype(apply(...))返回apply的第一个声明的类型。

我敢肯定,您现在很高兴知道为什么您的代码无法编译,但是我想如果您有一个可行的解决方案,您会更加开心。

我认为有一个基本事实需要首先阐明:apply operator()compose_impl模板的所有特殊化都具有相同的返回类型-在这种情况下,第一个仿函数的返回类型为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提供了左值参数(如本例所示),则functionTuple中的compose_impl将存储对这些参数的引用。这意味着只要使用了复合仿函数,原始仿函数就需要一直可用,否则您将有悬挂的引用。


  • 编辑:这是根据注释的要求提供的关于最后一个音符的更多信息:

    该行为归因于转发引用的工作方式-Fs&& ...compose函数参数。如果您具有F&&形式的函数参数,正在为其执行模板参数推导(如此处所示),并且为此参数给出了A类型的参数,则:
  • 如果参数表达式是一个右值,则将F推导为A,当替换回函数参数时,它会给出A&&(例如,如果您直接将lambda表达式作为参数传递给compose,则会发生这种情况);
  • 如果参数表达式是一个左值,则将F推导为A&,并将其替换回函数参数后,它会给出A& &&,根据reference collapsing规则产生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&>
    

    如果您将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;
    

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

    哇好久也许您不应该说“精心” :-)。

    编辑2,来自OP的第二条评论:是的,没有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++ - 递归可变参数函数模板的返回类型的decltype,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28571902/

    有关c++ - 递归可变参数函数模板的返回类型的decltype的更多相关文章

    1. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

      为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

    2. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

      exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

    3. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

      我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

    4. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

      我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

    5. ruby - RSpec - 使用测试替身作为 block 参数 - 2

      我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

    6. ruby - 如何在 Ruby 中拆分参数字符串 Bash 样式? - 2

      我正在为一个项目制作一个简单的shell,我希望像在Bash中一样解析参数字符串。foobar"helloworld"fooz应该变成:["foo","bar","helloworld","fooz"]等等。到目前为止,我一直在使用CSV::parse_line,将列分隔符设置为""和.compact输出。问题是我现在必须选择是要支持单引号还是双引号。CSV不支持超过一个分隔符。Python有一个名为shlex的模块:>>>shlex.split("Test'helloworld'foo")['Test','helloworld','foo']>>>shlex.split('Test"

    7. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

      我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

    8. ruby - Infinity 和 NaN 的类型是什么? - 2

      我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

    9. ruby - 检查方法参数的类型 - 2

      我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)

    10. ruby-on-rails - 在默认方法参数中使用 .reverse_merge 或 .merge - 2

      两者都可以defsetup(options={})options.reverse_merge:size=>25,:velocity=>10end和defsetup(options={}){:size=>25,:velocity=>10}.merge(options)end在方法的参数中分配默认值。问题是:哪个更好?您更愿意使用哪一个?在性能、代码可读性或其他方面有什么不同吗?编辑:我无意中添加了bang(!)...并不是要询问nobang方法与bang方法之间的区别 最佳答案 我倾向于使用reverse_merge方法:option

    随机推荐