[英]Deducing knowledge of original types, whilst simultaneously forwarding
簡介:我想以一個推斷出確切類型的函數作為結束,並采用(例如)一個將其轉發的元組(其類型將不同於調用該函數的確切類型)。
我一直試圖通過推導給定函數的參數類型來“知道”,同時轉發它們。 我想我可能缺少有關此工作原理的關鍵信息。
#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
本身的類型?
回答第一個問題:
函數的參數是表達式 ,而不是類型 。 兩者之間的區別在第5章[expr] p5中表示:
如果表達式最初的類型為“對T的引用”(8.3.2,8.5.3),則在進行任何進一步分析之前,將類型調整為T。
因此, g(str)
和g(sref)
之間沒有任何區別。 g()
始終會看到std::string
,而永遠不會看到引用。
另外,表達式可以是lvalue或rvalue(實際上是C ++ 11規則的簡化,但是對於本次討論而言已經足夠接近了-如果您想要詳細信息,請參見3.10 [basic.lval])。
回答第二個問題:
形式的模板參數:
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&&
轉換為A&&
(一個右值)並調用bar
。 的過載bar
,它接受一個右值A
然后選擇。 即,如果我們用右值調用foo
,則foo
用右值調用bar
。
但是,如果我們調用foo(a)
(一個左值A
),則T
推導為A&
。 現在,演員表看起來像:
static_cast<A& &&>(t);
根據該折疊規則,該規則簡化為:
static_cast<A&>(t);
即,將左值t
強制轉換為左值(無操作強制轉換),因此將調用采用左值的bar
過載。 即,如果我們用左值調用foo
,則foo
用左值調用bar
。 這就是術語“ 完美轉發”的來歷。
類型(甚至在C ++中)大多是編譯類型概念(當然,vtable中的RTTI除外)。
如果您需要完全動態的類型,那么C ++可能不是最佳的語言。
您可能會使用您想要的功能(例如提供額外的內置功能)來擴展GCC(實際上是g++
,假設至少為4.6),或者使用插件或GCC MELT擴展(MELT是擴展GCC的高級領域特定語言)。可以使用常量字符串等方式編碼其參數的類型),但這確實需要做一些工作(並且特定於GCC)。
但是我不明白為什么要在C語言中做這種巴洛克式的事情。如果動態類型對您如此重要,那么為什么不使用動態類型的語言呢?
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.