[英]Inheriting templated operator= in C++14: different behaviour with g++ and clang++
我有這個代碼與GCC 9.1一樣正常工作:
#include <type_traits>
template< typename T >
class A
{
protected:
T value;
public:
template< typename U,
typename...,
typename = std::enable_if_t< std::is_fundamental< U >::value > >
A& operator=(U v)
{
value = v;
return *this;
}
};
template< typename T >
class B : public A<T>
{
public:
using A<T>::operator=;
template< typename U,
typename...,
typename = std::enable_if_t< ! std::is_fundamental< U >::value > >
B& operator=(U v)
{
this->value = v;
return *this;
}
};
int main()
{
B<int> obj;
obj = 2;
}
(實際上,我們會在B::operator=
做一些奇特的事情,甚至為enable_if
使用不同的類型特征,但這是最簡單的可重現的例子。)
問題是thtat Clang 8.0.1給出了一個錯誤,不管怎樣,不考慮父類的operator=
,盡管孩子已經using A<T>::operator=;
:
test.cpp:39:9: error: no viable overloaded '='
obj = 2;
~~~ ^ ~
test.cpp:4:7: note: candidate function (the implicit copy assignment operator) not viable:
no known conversion from 'int' to 'const A<int>' for 1st argument
class A
^
test.cpp:4:7: note: candidate function (the implicit move assignment operator) not viable:
no known conversion from 'int' to 'A<int>' for 1st argument
class A
^
test.cpp:20:7: note: candidate function (the implicit copy assignment operator) not
viable: no known conversion from 'int' to 'const B<int>' for 1st argument
class B : public A<T>
^
test.cpp:20:7: note: candidate function (the implicit move assignment operator) not
viable: no known conversion from 'int' to 'B<int>' for 1st argument
class B : public A<T>
^
test.cpp:28:8: note: candidate template ignored: requirement
'!std::is_fundamental<int>::value' was not satisfied [with U = int, $1 = <>]
B& operator=(U v)
^
1 error generated.
哪個編譯器符合標准? (我正在使用-std=c++14
編譯。)我應該如何更改代碼以使其正確?
考慮這個簡化的代碼:
#include <iostream>
struct A
{
template <int n = 1> void foo() { std::cout << n; }
};
struct B : public A
{
using A::foo;
template <int n = 2> void foo() { std::cout << n; }
};
int main()
{
B obj;
obj.foo();
}
這兩個編譯器都應該打印2。
如果派生類已經具有相同簽名的類,則它隱藏或覆蓋using
聲明引入的類。 你的賦值運算符的簽名表面上是相同的。 考慮這個片段:
template <typename U,
typename = std::enable_if_t<std::is_fundamental<U>::value>>
void bar(U) {}
template <typename U,
typename = std::enable_if_t<!std::is_fundamental<U>::value>>
void bar(U) {}
這會導致兩個編譯器的bar
重新定義錯誤。
但是,如果更改其中一個模板中的返回類型,則錯誤消失!
現在是時候密切關注標准了。
當using-declarator將基類的聲明帶入派生類時,派生類中的成員函數和成員函數模板覆蓋和/或隱藏具有相同名稱的成員函數和成員函數模板,參數類型列表(11.3。 5),cv-qualification和基類中的ref-qualifier(如果有的話)(而不是沖突)。 這些隱藏或重寫的聲明被排除在using-declarator引入的聲明集之外
現在,就模板而言,這聽起來很可疑。 如何在不比較模板參數列表的情況下甚至比較兩個參數類型列表? 前者取決於后者。 的確,上面的一段說:
如果命名空間作用域或塊作用域中的函數聲明與using聲明引入的函數具有相同的名稱和相同的參數類型列表(11.3.5),並且聲明不聲明相同的函數,則程序為病態的。 如果命名空間作用域中的函數模板聲明具有相同的名稱,參數類型列表,返回類型和模板參數列表作為由using聲明引入的函數模板,則該程序是錯誤的。
這更有意義。 如果模板參數列表相同,則兩個模板是相同的,以及其他所有模板...但等等,這包括返回類型! 如果兩個模板的名稱和簽名中的所有內容( 包括返回類型 (但不包括默認參數值))相同,則它們是相同的。 然后一個人可以與另一個發生沖突或隱藏。
那么如果我們在B中更改賦值運算符的返回類型並使其與A中的相同,會發生什么? GCC停止接受該代碼 。
所以我的結論是這樣的:
using
的命名空間范圍和using
帶來的基類的名稱派生類。 using
在命名空間范圍內using
的規則,並將其應用於基類/派生類的上下文中。 注意: 我覺得這個答案是錯誤的, nm的答案是正確的。 我會保留這個答案,因為我不確定,但請去檢查答案。
Per [namespace.udecl] / 15 :
當using聲明將基類中的名稱帶入派生類范圍時,派生類中的成員函數和成員函數模板覆蓋和/或隱藏具有相同名稱的成員函數和成員函數模板,parameter-type-list([ dcl.fct]),cv-qualification和ref-qualifier (如果有的話)在基類中(而不是沖突)。
的operator=
在派生類聲明B
具有完全相同的名稱,參數類型列表,CV-資格(無),和REF-限定符 (無)作為一個中聲明A
。 因此,在B
聲明的那個隱藏了A
那個,並且代碼是格式錯誤的,因為重載解析沒有找到適當的調用函數。 但是,此處未解決模板參數列表。
他們應該考慮一下嗎? 這是標准變得不清楚的地方。 A
和B
被認為具有Clang的相同(模板)簽名,但不被GCC認可。 nm的答案指出真正的問題實際上在於返回類型。 (確定簽名時從不考慮默認模板參數。)
請注意,這是在名稱查找時決定的。 模板參數推斷尚未執行,也不是替換。 你不能說“哦,扣除/替換失敗,所以讓我們去添加更多成員到重載集”。 所以SFINAE在這里沒有什么不同。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.