总结:我希望最终得到一个函数,它推导出它所调用的精确类型,并采用(例如)一个转发它们的元组(其类型将与精确不同)函数被调用的类型。
我试图通过扣除给定函数的参数类型来“知道”,同时转发它们。我想我可能会遗漏一些至关重要的事情。
#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);
}
我希望看到的情况是,当我的函数(例如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*&
和g2
:
int, double, void*, std::string, char const*
int&, double&, void*&, std::string&, char const*&
我有两件事没有到达:
为什么没有打印(通过链接器错误)类型与我实际传入的内容相匹配? (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);
但至少可以说“笨拙”!
在g1
中,函数签名声明中&&
的出现似乎改变了模板参数Args
本身的类型。与之相比:
template <typename T>
void test(T t);
或者:
template <typename T>
void test(T& t);
使用以下任何一个:
int i;
test(i);
不会更改T
的类型。当&&
没有时,为什么T
会更改&
本身的类型?
答案 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&&);
很特别。它们与T
,T&
甚至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中做这样的巴洛克式事情。如果动态打字对你来说如此重要,你为什么不使用动态类型的语言?