[英]Specialized template function with deleted “general” case fails to compile with g++ <=4.8.0 and clang++
[英]Which is the more specialized template function? clang and g++ differ on that
在使用可變參數模板時,遵循這個SO問題 (注意:並不是強制要求遵循這個問題),對於以下模板重載函數,我得到了clang(3.8)和g ++(6.1)的不同行為:
template <class... Ts>
struct pack { };
template <class a, class b>
constexpr bool starts_with(a, b) {
return false;
}
template <template <typename...> class PACK_A,
template <typename...> class PACK_B, typename... Ts1, typename... Ts2>
constexpr bool starts_with(PACK_A<Ts1..., Ts2...>, PACK_B<Ts1...>) {
return true;
}
int main() {
std::cout << std::boolalpha;
std::cout << starts_with(pack<int, float, double>(),
pack<float, int, double>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(),
pack<int, float, double, int>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(),
pack<int, float, int>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(),
pack<int, float, double>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(),
pack<int>()) << std::endl;
}
代碼: http : //coliru.stacked-crooked.com/a/b62fa93ea88fa25b
|---|-----------------------------------------------------------------------------|
| # |starts_with(a, b) | expected | clang (3.8) | g++ (6.1) |
|---|-----------------------------------|-------------|-------------|-------------|
| 1 |a: pack<int, float, double>() | false | false | false |
| |b: pack<float, int, double>() | | | |
|---|-----------------------------------|-------------|-------------|--------- ---|
| 2 |a: pack<int, float, double>() | false | false | false |
| |b: pack<int, float, double, int>() | | | |
|---|-----------------------------------|-------------|-------------|--------- ---|
| 3 |a: pack<int, float, double>() | false | false | false |
| |b: pack<int, float, int>() | | | |
|---|-----------------------------------|-------------|-------------|--------- ---|
| 4 |a: pack<int, float, double>() | true | true | false |
| |b: pack<int, float, double>() | | | |
|---|-----------------------------------|-------------|-------------|--------- ---|
| 5 |a: pack<int, float, double>() | true | false | false |
| |b: pack<int>() | | | |
|---|-----------------------------------------------------------------------------|
最后兩個案例(4和5)存在問題:我對更專業模板的期望是錯誤的嗎? 如果是這樣,誰是正確的4,clang或g ++? (請注意,代碼編譯時沒有任何錯誤或警告,但結果不同)。
試圖自己回答這個問題,我多次通過規范中的“更專業”規則(14.5.6.2功能模板的部分排序)和cppreference - 似乎更專業的規則應該給出我期待的結果(如果不是,可能會出現歧義錯誤,但事實並非如此)。 那么,我在這里錯過了什么?
等等(1):請不要急於帶上Herb Sutter的“ 不要超載模板 ”和他的模板方法測驗 。 這些肯定很重要,但語言仍然允許模板重載! (這確實是一個強化點,為什么你不應該重載模板 - 在某些邊緣情況下,它可能會混淆兩個不同的編譯器,或者使程序員感到困惑。但問題不在於是否使用它,它是: 什么是如果你使用它,這是正確的行為嗎? )。
等(2):請不要急於帶來其他可能的解決方案。 肯定有。 這里有兩個: 一個是內部結構 , 另一個是內部靜態方法 。 兩者都是合適的解決方案,都按預期工作,但有關上述模板重載行為的問題仍然存在。
正如Holt所說,當涉及到可變參數模板參數推導時,標准是非常嚴格的:
14.8.2.5/9
如果P具有包含T或i的形式,則將相應模板參數列表P的每個參數Pi與對應的模板參數列表A的對應參數Ai進行比較。 如果P的模板參數列表包含包擴展,則不是最后一個模板參數,整個模板參數列表是非推導的上下文。 如果Pi是包擴展,則將Pi的模式與A的模板參數列表中的每個剩余參數進行比較。每個比較推導出由Pi擴展的模板參數包中的后續位置的模板參數。
這由TC解釋意味着Ts1...
可以從第二個參數中推導出來,但它沒有留下Ts2...
演繹的空間。 因此,顯然clang將在這里正確,gcc將是錯誤的...... 只有當第二個參數包含完全相同的模板參數時才應選擇重載,例如:
starts_with(pack<int, float, double>(), pack<int, float, double>())
仍然是示例5.不滿足此要求,並且不允許編譯器選擇過載。
僅供參考:不是答案。 這是對評論中的問題的回答:
在gcc5.3上進行以下小改動會導致它產生預期的結果,或至少與clang相同的結果。
rhodges@dingbat:~$ cat nod.cpp
#include <iostream>
using namespace std;
template <class... Ts>
struct pack { };
template <class a, class b>
constexpr bool starts_with(a, b) {
return false;
}
template <typename... Ts1, typename... Ts2 >
constexpr bool starts_with(pack<Ts1..., Ts2...>, pack<Ts1...>) {
return true;
}
int main() {
std::cout << std::boolalpha;
std::cout << starts_with(pack<int, float, double>(), pack<float, int, double>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(), pack<int, float, double, int>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(), pack<int, float, int>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(), pack<int, float, double>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(), pack<int>()) << std::endl;
}
rhodges@dingbat:~$ g++ -std=c++14 nod.cpp && ./a.out
false
false
false
true
false
rhodges@dingbat:~$ g++ --version
g++ (Ubuntu 5.3.1-14ubuntu2.1) 5.3.1 20160413
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
rhodges@dingbat:~$
並且為了記錄,修改程序以評估推斷的上下文中的所有包在兩個平台上都取得了成功:
rhodges@dingbat:~$ cat nod.cpp
#include <iostream>
using namespace std;
template <class... Ts>
struct pack { };
template <class a, class b>
constexpr bool starts_with_impl(a, b) {
return false;
}
template<typename...LRest>
constexpr bool starts_with_impl(pack<LRest...>, pack<>)
{
return true;
}
template<typename First, typename...LRest, typename...RRest>
constexpr bool starts_with_impl(pack<First, LRest...>, pack<First, RRest...>)
{
return starts_with_impl(pack<LRest...>(), pack<RRest...>());
}
template <typename... Ts1, typename... Ts2 >
constexpr bool starts_with(pack<Ts2...> p1, pack<Ts1...> p2) {
return starts_with_impl(p1, p2);
}
int main() {
std::cout << std::boolalpha;
std::cout << starts_with(pack<int, float, double>(), pack<float, int, double>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(), pack<int, float, double, int>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(), pack<int, float, int>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(), pack<int, float, double>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(), pack<int>()) << std::endl;
}
rhodges@dingbat:~$ g++ -std=c++14 nod.cpp && ./a.out
false
false
false
true
true
感謝WF指導我這個方向。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.