演绎原始类型的知识,同时转发

Fle*_*exo 8 c++ templates forwarding c++11 template-argument-deduction

简介:我希望最终得到一个函数,该函数推导出它所调用的确切类型,并采用(例如)转发它们的元组(其类型将与调用函数的确切类型不同).

我试图通过扣除给定函数的参数类型来"知道",同时转发它们.我想我可能会遗漏一些关于它是如何工作的关键.

#include <tuple>
#include <string>
#include <functional>

template <typename ...Args>
struct unresolved_linker_to_print_the_type {
   unresolved_linker_to_print_the_type();
};

void f(int,double,void*,std::string&,const char*) {
}

template <typename F, typename ...Args>
void g1(F func, Args&&... args) {
  unresolved_linker_to_print_the_type<Args...>();
  auto tuple = std::forward_as_tuple(args...);
  unresolved_linker_to_print_the_type<decltype(tuple)>();
}

template <typename F, typename T, typename ...Args>
void g2(F func, const T& tuple, Args... args) {
  unresolved_linker_to_print_the_type<Args...>();
  unresolved_linker_to_print_the_type<decltype(tuple)>();
}

int main() {
  int i;
  double d;
  void *ptr;
  std::string str;
  std::string& sref = str;
  const char *cstr = "HI";

  g1(f, i,d,ptr,sref,cstr);
  g2(f, std::forward_as_tuple(i,d,ptr,sref,cstr),  i,d,ptr,sref,cstr);
}
Run Code Online (Sandbox Code Playgroud)

我想看到的是一个场景,当我的函数(例如g1或者g2)被调用时,它知道并且可以同时使用原始类型 - int,double,void*,std::string&,const char* 以及转发的句子.

在这种情况下,我似乎无法从内部g1或内部找到此信息g2.(故意,打印出类型)链接器错误告诉我g1他们是:

int&, double&, void*&, std::string&, char const*&
int&, double&, void*&, std::string&, char const*&
Run Code Online (Sandbox Code Playgroud)

并在g2:

int, double, void*, std::string, char const*
int&, double&, void*&, std::string&, char const*&
Run Code Online (Sandbox Code Playgroud)

我有两件事没有到达:

  1. 为什么没有打印(通过链接器错误)类型匹配我实际传入的内容?(int,double,void*,std::string&,const char).我可以推断出我实际上通过了什么吗?优选地具有"自然"语法,即一切只是一次而没有明确写出.我可以明确地写:

    g2<decltype(&f),decltype(std::forward_as_tuple(i,d,ptr,sref,cstr)),int,double,void*,std::string&,const char*>(f,std::forward_as_tuple(i,d,ptr,sref,cstr),i,d,ptr,sref,cstr);
    
    Run Code Online (Sandbox Code Playgroud)

    但至少可以说这是"笨拙的"!

  2. g1存在&&的函数签名的声明似乎改变类型的模板参数Args本身.与之相比:

    template <typename T>
    void test(T t);
    
    Run Code Online (Sandbox Code Playgroud)

    要么:

    template <typename T>
    void test(T& t);
    
    Run Code Online (Sandbox Code Playgroud)

    使用以下任何一个:

    int i;
    test(i);
    
    Run Code Online (Sandbox Code Playgroud)

    不改变类型T.为什么不&&改变T自己的类型&

How*_*ant 2

回答第一个问题:

\n\n

函数的参数是表达式,而不是类型。这两者之间的区别在第 5 章 [expr], p5 中表达:

\n\n
\n

如果表达式最初的类型为 \xe2\x80\x9creference to T\xe2\x80\x9d (8.3.2,\n 8.5.3),则在任何进一步分析之前将类型调整为 T。

\n
\n\n

g(str)因此,和之间没有任何区别g(sref)g()总是看到std::string, 而从未看到引用。

\n\n

另外,表达式可以是左值或右值(实际上这是 C++11 规则的简化,但对于本次讨论来说已经足够接近了 - 如果您想要详细信息,请参阅 3.10 [basic.lval])。

\n\n

回答第二个问题:

\n\n

表单的模板参数:

\n\n
template <class T>\nvoid g(T&&);\n
Run Code Online (Sandbox Code Playgroud)\n\n

很特别。它们与TT&、 甚至const T&&在以下方面有所不同:

\n\n

T&&绑定到左值时,T被推导为左值引用类型,否则T完全按照正常推导规则进行推导。

\n\n

例子:

\n\n
int i = 0;\ng(i);  // calls g<int&>(i)\ng(0);  // calls g<int>(0)\n
Run Code Online (Sandbox Code Playgroud)\n\n

此行为是为了支持所谓的完美转发,通常如下所示:

\n\n
struct A{};\n\nvoid bar(const A&);\nvoid bar(A&&);\n\ntemplate <class T>\nvoid foo(T&& t)\n{\n     bar(static_cast<T&&>(t));  // real code would use std::forward<T> here\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果调用foo(A())(一个右值A),则T根据正常规则推论为A。在内部,foo我们转换tA&&(右值)并调用bar。然后选择bar带有右值的重载。A即,如果我们使用foo右值调用,则使用右值foo调用。bar

\n\n

但如果我们调用foo(a)(an lvalue A),则T推导出为A&。现在演员阵容如下:

\n\n
static_cast<A& &&>(t);\n
Run Code Online (Sandbox Code Playgroud)\n\n

根据参考折叠规则简化为:

\n\n
static_cast<A&>(t);\n
Run Code Online (Sandbox Code Playgroud)\n\n

即,左值t被转换为左值(无操作转换),因此bar调用采用左值的重载。即,如果我们使用foo左值调用,则使用左值foo调用。bar这就是完美转发一词的由来。

\n