[英]Template parameters of function type with auto return type arguments of previous template parameter types
我有一個帶有兩個參數的模板:第一個是類型,第二個是 function 指針,其參數的類型是第一個模板參數。 此 MCVE 有效:
void returnsVoid(int x) { }
template <typename T, void (*Func)(T)>
struct foo { void bar(T t) { Func(t); } };
int main(int, char *[]) {
foo<int, returnsVoid> a; // ok
}
但是,當我將第二個模板參數的返回類型更改為auto
時(如相關問題中所述),我收到錯誤消息:
void returnsVoid(int x) { }
template <typename T, auto (*Func)(T)>
struct foo {
void bar(T t) { Func(t); }
};
int main(int, char *[]) {
foo<int, returnsVoid> a; // error: unable to deduce ‘auto (*)(T)’ from ‘returnsVoid’
// note: mismatched types ‘T’ and ‘int’
}
為什么這不再適用於auto
返回類型?
我在 Ubuntu Linux 機器上使用 g++ 9.3.0。 clang 10 中出現類似錯誤。
這是 gcc 的錯誤。 (錯誤 83417 )
當使用第一個模板參數T
作為 function 參數時,似乎 gcc 未能推斷出auto
的類型; 您可以使用std::type_identity
(C++20 起) 將其排除在參與模板參數推導之外。
// workaround for gcc
template <typename T, auto (*Func)(std::type_identity_t<T>)>
struct foo {
void bar(T t) { Func(t); }
};
順便說一句: Clang 10似乎運行良好。
除非另有說明,否則以下所有標准參考均指N4861(2020 年 3 月布拉格后工作草案/C++20 DIS) 。
跳轉到A 解決方法,以緩解本文底部的 GCC 錯誤部分,以獲得一種解決方法,被 GCC 和 Clanng 接受,它仍然依賴於依賴類型的推斷,以避免客戶端必須為實際類型指定第二個模板參數與“main”模板參數相關聯的 function 參數; 即 function 指針使用了關聯的非類型模板參數。
當從表達式推導出與以依賴類型聲明的非類型模板形參
P
對應的實參的值時,從值的類型推導出P
類型中的模板形參。 [示例:template<long n> struct A { }; template<typename T> struct C; template<typename T, T n> struct C<A<n>> { using Q = T; }; using R = long; using R = C<A<2>>::Q; // OK; T was deduced as long from the // template argument value in the type A<2>
—結束示例]
經歷類型推導(從表達式)的非類型模板參數的聲明中的任何依賴類型也應從非類型模板參數的關聯參數推導。 同樣重要的是[temp.deduct.type]/5 ,涵蓋非推導上下文,不適用於 function 指針的非類型模板參數中的依賴類型的一般用途; 在 OP 的示例中, T
是一個依賴類型,因此是從非類型(函數指針)模板參數的參數值推導出來的。
當一個給定的,比如說,類型模板參數是從多個來源推導出來的(例如在 OP 的例子中),一個常見的問題是推導產生不同的類型。 例如,如以下博客文章所示:
template<typename T> struct Foo { T t; }; template<typename T> void addToFoo(Foo<T>& foo, T val) { foo.t += val; } int main() { Foo<long> f{42}; addToFoo(f, 13); // error: no matching function for call to 'addToFoo' // note: candidate template ignored: deduced conflicting // types for parameter T (long vs. int). return 0; }
正如@songyuanyao: answer中所顯示的那樣(也如博客文章中所示),對於多個推導源產生沖突的情況,可以使用類型標識轉換特征有意地將給定的模板參數放置在非推導上下文中結果。
但是,OP:s 失敗的根本原因不是推導結果沖突(這是一條紅鯡魚),而是 GCC:s 未能正確推導模板參數,從推導另一個模板參數時,前者作為依賴項存在類型。
因此,如果我們將 go 返回到 [temp.deduct.type]/13,對於以下 class 模板和隨后的部分特化:
// #1
template <auto>
struct A { static void dispatch() = delete; };
// #2
template <typename T, void (*fun)(T)>
struct A<fun> {
static void dispatch() {
T t{};
fun(t);
}
};
以下:
A<f>::dispatch(); // #3
如果f
是一個 function 具有一個返回void
的單個參數(類型是默認可構造的),則格式正確,例如
void f(int) { std::cout << "void f(int)\n"; }
// -> #3 is well-formed
因為這將匹配#2
處的部分特化,將T
推導出為int
和非類型模板參數(它決定由主模板制定的特化藍圖),在此部分特化中依賴於T
,以void(*)(int)
。
另一方面,如果f
不返回void
,則#3
格式不正確,因為#2
的部分特化不再可行。
void f(int) { std::cout << "int f(int)\n"; }
// -> #3 is ill-formed
這里的關鍵是:
#2
的部分特化僅適用於與特化的第二個(非類型)模板參數匹配的模板 arguments 到A
,並且T
是從第二個(非類型)模板參數的推導推導出來的,因為T
是第二個模板參數聲明中的依賴類型。對於上述兩種情況,GCC 和 Clang 都按預期工作。
現在,如果我們考慮與上面的#1
和#2
類似的例子:
// #4
template <auto>
struct B { static void dispatch() = delete; };
// #5
template <typename T, auto (*fun)(T)>
struct B<fun> {
static void dispatch() {
T t{};
fun(t);
}
};
// ... elsewere
// #6
B<f>::dispatch();
與上述相同的論點適用:
f
引用了一個 function (現在限制較少),它有一個參數(類型是默認可構造的),那么#5
的部分特化是可行的,它的第二個非類型模板參數,它包含其第一個類型模板參數作為依賴類型,應用於推導后者。這種情況下的顯着區別是非類型模板參數的類型本身經歷了[temp.arg.nontype]/1 :
如果模板參數的類型
T
包含占位符類型 ([dcl.spec.auto]) 或推導的 class 類型的占位符 ([dcl.type.class.deduct]),則參數的類型是類型為發明聲明中的變量x
推導出來T x = template-argument;
如果模板參數聲明([temp.param])不允許推導參數類型,則程序格式錯誤。
但是 [temp.deduct.type]/13 仍然適用,而且我們可能不是 [temp.deduct.type]/13 實際上是作為P0127R2的一部分添加的(使用auto
聲明非類型模板參數),它引入了占位符類型對於 C++17 的非類型模板參數。
因此,核心問題是當聲明依賴類型(要推導)的非類型模板參數是function 指針(或者,如鏈接到 GCC 錯誤報告中所示,作為指向成員的指針)並且使用占位符類型( auto
)聲明非類型模板參數。
@songyuanyao:答案顯示了此錯誤的解決方法,應用於 OP 的示例,只需使依賴類型不依賴,因為可以從其他地方推導出關聯的模板參數(即從 OP 示例中的第一個模板參數)。 這對於上面的例子是行不通的,我們在推演非類型模板參數時僅僅依靠依賴類型推導來找到類型模板參數的類型(即前者中的依賴類型)。
對於一個實際的客戶端 API,要求客戶端明確指定參數的類型給作為另一個參數提供的 function,當前者完全可以從后者推導出來時,可以說是冗余設計,並且在提供時會導致客戶端混淆這兩個模板參數的 arguments 沖突。 因此,可以說我們希望依靠上面顯示的部分專業化技術,但如這些答案所示,GCC 在這方面失敗了,以防我們希望客戶端不限於特定的返回類型。
但是,我們可以通過使用與上述相同的方法來解決這個問題,仍然依賴 [temp.deduct.type]/13,但在部分特化中使用附加的類型模板參數(用於返回類型):
#include <iostream>
template <auto>
struct C { static void dispatch() = delete; };
template <typename T, typename Return, Return (*fun)(T)>
struct C<fun> {
static void dispatch() {
T t{};
fun(t);
}
};
void f(int) { std::cout << "void f(int)\n"; }
int g(int) { std::cout << "int f(int)\n"; return 0; }
int main() {
C<f>::dispatch();
C<g>::dispatch();
}
客戶端不需要擔心部分特化的附加模板參數,因為它們完全可以通過特化的第三個非類型模板參數推導出來,它們依賴於其聲明。 最后一個示例被 GCC 和 Clang 接受。
這是CWG2476 :[dcl.spec.auto]/2 要求僅在 function 聲明符聲明 function 的情況下,將auto
用作(部分)沒有尾隨返回類型的返回類型。 模板參數聲明不這樣做,所以它是無效的。 有人可能會爭辯說 /5 無論如何都允許它在模板參數的decl-specifier-seq中使用,但這沒有意義,因為它會剝奪以下含義:
template<auto g() -> int>
int f() {return g();}
也就是說,扣除規則在這里會起作用,所以這是措辭中的一個缺陷,可能會通過說返回類型扣除只是不會發生在auto
出於其他原因而允許的情況下發生。
正如@songyuanyao 所述,這似乎是一個 gcc 錯誤。
對於 <= c++17 一種解決方案是將類型 T 從 function 簽名移動到非推斷上下文:
template <typename T>
struct identity { using type = T; };
接着
template <typename T, auto (*Func)(typename identity<T>::type)>
struct foo {
void bar(T t) { Func(t); }
};
另一種方法是更改設計並移至成員 function 模板。 自動類型扣除在這種情況下有效:
void returnsVoid(int) { }
template <typename T>
struct foo {
template <auto (*Func)(T)>
void bar(T t) { Func(t); }
};
int main(int, char *[]) {
foo<int> a;
a.bar<returnsVoid>(3);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.