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

时间:2011-10-25 13:43:45

标签: c++ c++11 templates forwarding template-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);
}

我希望看到的情况是,当我的函数(例如g1g2)被调用时,它知道并且可以使用两种原始类型 - int,double,void*,std::string&,const char* 转发的行政区。

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

int&, double&, void*&, std::string&, char const*&
int&, double&, void*&, std::string&, char const*&

g2

int, double, void*, std::string, char const*
int&, double&, void*&, std::string&, char const*&

我有两件事没有到达:

  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);
    

    但至少可以说“笨拙”!

  2. g1中,函数签名声明中&&的出现似乎改变了模板参数Args本身的类型。与之相比:

    template <typename T>
    void test(T t);
    

    或者:

    template <typename T>
    void test(T& t);
    

    使用以下任何一个:

    int i;
    test(i);
    

    不会更改T的类型。当&&没有时,为什么T会更改&本身的类型?

2 个答案:

答案 0 :(得分:2)

回答第一个问题:

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

中有所体现
  

如果表达式最初具有“引用T”类型(8.3.2,   8.5.3),在进一步分析之前将类型调整为T.

因此,g(str)g(sref)之间没有任何区别。 g()总是看到std::string,而不是参考。

此外,表达式可以是左值或右值(实际上这是C ++ 11规则的简化,但它足够接近这个讨论 - 如果你想要它们在3.10 [basic.lval]中的细节)。 / p>

回答第二个问题:

表单的模板参数:

template <class T>
void g(T&&);

很特别。它们与TT&甚至const T&&不同,具体如下:

T&&绑定到左值时,T被推导为左值引用类型,否则T将根据正常的扣除规则进行精确推断。

示例:

int i = 0;
g(i);  // calls g<int&>(i)
g(0);  // calls g<int>(0)

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

struct A{};

void bar(const A&);
void bar(A&&);

template <class T>
void foo(T&& t)
{
     bar(static_cast<T&&>(t));  // real code would use std::forward<T> here
}

如果有人调用foo(A())(右值A),则T按正常规则推导为A。在foo内,我们将t投射到A&&(右值)并致电bar。然后选择带有右值bar的{​​{1}}的重载。即如果我们使用右值调用A,则foo会使用右值调用foo

但是,如果我们拨打bar(左值foo(a)),则A会推断为T。演员看起来像:

A&

在参考折叠规则下简化为:

static_cast<A& &&>(t);

即。左值static_cast<A&>(t); 被强制转换为左值(无操作投射),因此调用左值的t重载。即如果我们使用左值调用bar,则foo会使用左值调用foo。这就是完美转发一词的来源。

答案 1 :(得分:0)

类型(即使在C ++中)主要是编译类型的概念(当然除了vtable中的RTTI)。

如果你需要完全动态的类型,那么C ++可能不是最好的语言。

您可能使用插件GCC MELT扩展名扩展GCC(实际为g++,假设它至少为4.6)(MELT是一种高级域特定语言扩展GCC)它可以做你想要的(例如提供一个额外的内置函数,在一些常量字符串中编码其参数的类型等等),但这需要一些工作(并且特定于GCC)。 / p>

但是我不明白为什么你想在C中做这样的巴洛克式事情。如果动态打字对你来说如此重要,你为什么不使用动态类型的语言?