【问题标题】:Template deduction for function based on its return type?基于返回类型的函数模板推导?
【发布时间】:2010-04-10 10:09:41
【问题描述】:

我希望能够使用模板推演来实现以下目标:

GCPtr<A> ptr1 = GC::Allocate();
GCPtr<B> ptr2 = GC::Allocate();

而不是(我目前拥有的):

GCPtr<A> ptr1 = GC::Allocate<A>();
GCPtr<B> ptr2 = GC::Allocate<B>();

我当前的分配函数如下所示:

class GC
{
public:
    template <typename T>
    static GCPtr<T> Allocate();
};

这是否可以取消额外的&lt;A&gt;&lt;B&gt;

【问题讨论】:

  • fwiw 我有类似的东西,但返回类型基于构造函数参数的类型。我做了一个模板化的帮助函数make_complex_template_obj(the, args),所以我可以在从该函数初始化变量时使用auto。大概出于与接受的答案相同的原因,该模板无法给出auto 返回类型。谢天谢地,我可以避免在 return 中重复类型名,因为到那时它知道即将到来的类型并适当地转换了一个裸 initialiser-list。相当冒险!

标签: c++ templates template-argument-deduction


【解决方案1】:

那是做不到的。返回类型不参与类型推导,而是已经匹配了适当的模板签名的结果。但是,您可以将其从大多数用途中隐藏起来:

// helper
template <typename T>
void Allocate( GCPtr<T>& p ) {
   p = GC::Allocate<T>();
}

int main()
{
   GCPtr<A> p = 0;
   Allocate(p);
}

该语法实际上是否比最初的 GCPtr&lt;A&gt; p = GC::Allocate&lt;A&gt;() 更好或更差是另一个问题。

附: c++11 将允许您跳过其中一种类型声明:

auto p = GC::Allocate<A>();   // p is of type GCPtr<A>

【讨论】:

    【解决方案2】:

    我唯一能想到的就是:让 Allocate 成为一个非模板,它返回一个非模板代理对象,该对象具有一个模板化的转换运算符,它可以完成真正的工作:

    template <class T>
    struct GCPtr
    {
    
    };
    
    class Allocator
    {
    public:
        template <class T>
        operator GCPtr<T>() { return GCPtr<T>(); }
    };
    
    class GC
    {
    public:
        static Allocator Allocate() { return Allocator(); }//could give a call-back pointer?
    };
    
    int main()
    {
        GCPtr<int> p = GC::Allocate();
    }
    

    【讨论】:

    • 这似乎有点矫枉过正,但我​​仍然不知道这种模式。你教会了我一些东西。所以+1。
    • 无论如何,乍一看,我想你可以完全避免 GC::Allocate() 并写:GCPtr&lt;int&gt; p = Allocator() ;,不是吗?
    • 正如评论所说,分配器对象可以存储它通过构造函数接收的附加数据,因此 GC::Allocate 可以决定它需要哪些数据来执行操作。 - 最终GCPtr&lt;T&gt; 的构造函数可以自己完成工作(调用GC::Allocate&lt;T&gt;)。
    • 有没有办法将参数传递给分配器?类似godbolt.org/z/nScp8c
    【解决方案3】:

    你可以走相反的路线。

    如果您使用的是最新的编译器(MSVC 2010 应该会在几天后发布,或者当前版本的 GCC)并且不介意依赖 C++0x 功能:

    auto ptr1 = GC::Allocate<A>();
    auto ptr2 = GC::Allocate<B>();
    

    会为您节省额外的 &lt;A&gt;&lt;B&gt;,只是不在右侧。 :)

    【讨论】:

      【解决方案4】:

      (此答案与@UncleBens 相同,但更通用一点,因为它可以完美转发任何参数。)

      这在 haskell 等语言中非常有用,例如,read 将字符串作为输入,并根据所需的返回类型对其进行解析。

      (这里是sample code on ideone。)

      首先,从函数foo开始,我们希望推断其返回类型:

      template<typename Ret>
      Ret foo(const char *,int);
      template<>
      std::string foo<std::string>(const char *s,int) { return s; }
      template<>
      int         foo<int        >(const char *,int i) { return i; }
      

      当要求输入字符串时,它会返回第一个参数中的字符串。当要求输入 int 时,它会返回第二个参数。

      我们可以定义一个函数auto_foo,可以如下使用:

      int main() {
              std::string s = auto_foo("hi",5); std::cout << s << std::endl;
              int         i = auto_foo("hi",5); std::cout << i << std::endl;
      }
      

      为了完成这项工作,我们需要一个对象来临时存储函数参数,并在要求 convert 到所需的返回类型时运行函数:

      #include<tuple>
      
      template<size_t num_args, typename ...T>
      class Foo;
      template<typename ...T>
      class Foo<2,T...> : public std::tuple<T&&...>
      {
      public: 
              Foo(T&&... args) :
                      std::tuple<T&&...>(std::forward<T>(args)...)
              {}
              template< typename Return >
              operator Return() { return foo<Return>(std::get<0>(*this), std::get<1>(*this)); }
      };
      template<typename ...T>
      class Foo<3,T...> : std::tuple<T&&...>
      {
      public: 
              Foo(T&&... args) :
                      std::tuple<T&&...>(std::forward<T>(args)...)
              {}
              template< typename Return >
              operator Return() { return foo<Return>(std::get<0>(*this), std::get<1>(*this), std::get<2>(*this)); }
      };
      
      template<typename ...T>
      auto
      auto_foo(T&&... args)
              // -> Foo<T&&...> // old, incorrect, code
              -> Foo< sizeof...(T), T&&...> // to count the arguments
      {
              return              {std::forward<T>(args)...};
      }
      

      此外,上述方法适用于两个参数或三个参数的函数,不难看出如何扩展它。

      要写很多代码!对于您将应用它的每个函数,您可以编写一个宏来为您执行此操作。文件顶部的类似内容:

      REGISTER_FUNCTION_FOR_DEDUCED_RETURN_TYPE(foo); // declares
                              // necessary structure and auto_???
      

      然后你可以在你的程序中使用auto_foo

      【讨论】:

      • 我觉得这很有趣,但我相信你缺少 auto_foo 中的专业化参数:auto auto_foo(T&amp;&amp;... args) -&gt; Foo&lt;sizeof...(T), T&amp;&amp;...&gt;,否则它不会选择专业化恕我直言。
      • 你是对的。我将在这里更新代码。我已经在我的电脑上测试过代码,但显然我没有完全复制它。谢谢!
      • 无论如何,这是实现这一点的好方法。谢谢你的例子。
      • 有趣的解决方案,您选择 std::tuple_size 而不是直接使用 sizeof...(T) 有什么原因吗?
      • 没有理由,@daminetreg。我现在已经改了。我只是从我的工作示例中复制并粘贴了它,我不知道我是怎么写的! (更新:我可能首先尝试了sizeof(T)...,认为... 总是出现在应该发生扩展的表达式的末尾。但这不是那样的,所以也许这就是我选择的原因tuple_size 代替)
      【解决方案5】:

      同样你不能在返回类型上重载函数,你不能对它做模板推导。出于同样的原因 - 如果 f() 是返回某些内容的模板/重载,那么在这里使用什么类型:

      f();
      

      【讨论】:

      • 嗯,我已经考虑过了。我的垃圾收集器类使用引用计数,调用 GC::Allocate() 将固有地有 0 个引用,无论如何都会被清理掉。这当然是如果代码编译/
      • 编译器错误,除非出现在演员表中 ((int)f();) ...?
      • @UncleBens:好主意!但是,C++ 编译器目前不能以这种方式工作。
      • @Neil,我想说的是我已经想过当 f() 被自己调用时会发生什么(编译错误)。现在用 GC::Allocate() 替换 f() 并想象它确实编译了。我的垃圾收集器使用引用计数,由于返回值没有存储在 GCPtr 中,所以引用计数为 0,垃圾收集器会立即清理它。这都是假设的,因为代码实际上并没有编译。
      • @Neil:我的意思是,这就是基于返回类型的重载和类型推导假设如果存在的话。
      【解决方案6】:

      您可以尝试使用宏。除此之外,我看不出只用一个语句应该如何工作。

      #define ALLOC(ptrname,type) GCPtr<type> ptrname = GC::Allocate<type>()
      
      ALLOC(ptr1,A);
      

      Johannes 的观点是有效的。 >> 问题很容易解决。但我认为将逗号作为类型的一部分需要 C99 预处理器可变参数扩展:

      #define ALLOC(ptrname,...) GCPtr< __VA_ARGS__ > ptrname = GC::Allocate< __VA_ARGS__ >()
      
      ALLOC(ptr1,SomeTemplate<int,short>);
      

      【讨论】:

      • 请注意,如果您执行ALLOC(ptr1, A&lt;a, b&gt;);,则此宏将失败(有两个问题:type(又名'&gt;&gt;)后没有空格,并且逗号从A&lt;a, b&gt; 中生成了两个宏参数)。
      • 那会给你带来什么?您仍然必须提及类型,并且它不如 David 的使用内联函数模板的解决方案安全。 -1 来自我。
      • 您可以通过说ALLOC(ptr1, (A&lt;a, b&gt;)); 并重写宏以将函数类型传递给template&lt;typename T&gt; struct ty; template&lt;typename Ty&gt; struct ty&lt;void(Ty)&gt; { typedef Ty type; }; 并改为说GCPtr&lt;ty&lt;void type&gt;::type&gt; ptrname 来解决这两个问题(在模板中使用typename 也是如此) . C++0x 和一些当前的 c++03 编译器允许 typename 也在模板之外)。
      • @sbi:当然,我不会在我的代码中使用这样的宏。那是我唯一想到的。当然,即使使用 Davids 解决方案,您也必须至少命名一次类型。
      • @ltb:解决 C99 可变参数宏非常聪明。但是如果类型依赖于模板参数,它确实存在你需要两个版本的问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-11
      相关资源
      最近更新 更多