2016-06-14 90 views
11

考慮下面的代碼的靜態constexpr數據成員初始化的基類的一個靜態constexpr數據成員:通過使用派生類

template<typename T> 
struct S { static constexpr int bar = T::foo; }; 

struct U: S<U> { static constexpr int foo = 42; }; 

int main() { } 

GCC v6.1編譯它,clang 3.8與錯誤拒絕它

2 : error: no member named 'foo' in 'U'
struct S { static constexpr int bar = T::foo; };

哪個編譯器是正確的?
難道這是因爲Uis not a complete type在我們嘗試在S內使用它嗎?
在這種情況下,應該考慮GCC的錯誤,但我想知道,如果我是對前搜索/上bug跟蹤系統打開一個問題...

編輯

同時,我已向GCC開放了一個bug
等待它接受答案。

+1

標準工作草案的最新一批更新包括對與*內聯變量*的新概念有關的[9.2.3.2p3]的更改*('constexpr'變量現在隱含*內聯*,就像函數一樣),所以C++ 17的答案可能會改變;目前的仍然適用於C++ 14及更低版本。我將等待最新版本的規範在官方郵件中發佈,然後我將使用C++ 17特定信息更新答案。 – bogdan

+0

@bogdan哇,非常感謝。非常感謝。 – skypjack

+0

答覆已更新。 – bogdan

回答

5

對於C++ 14和11,Clang是對的;然而,最新的工作草案(未來的C++ 17)已經發生了變化 - 請參閱下一節。

標準引用查找是(從N4140,最接近C++ 14草案):

[temp.inst]/1:

[...] The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions, default arguments, or exception-specifications of the class member functions, member classes, scoped member enumerations, static data members and member templates; [...]

[temp.point]/4:

For a class template specialization, [...] the point of instantiation for such a specialization immediately precedes the namespace scope declaration or definition that refers to the specialization.

因此,實例爲S<U>的一點是U聲明之前正確,只有一個向前聲明struct U;概念之前插入,從而使名稱U被發現。

[class.static.data]/3:

[...] A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. [...] The member shall still be defined in a namespace scope if it is odr-used (3.2) in the program and the namespace scope definition shall not contain an initializer.

根據上面引述的段落中,barS定義範圍內的聲明,即使它有一個初始化,目前還只是一個聲明,而不是一個定義,所以它在S<U>被隱式實例化時被實例化,並且當時沒有U::foo

解決方法是使bar成爲函數;根據第一個引用,函數的定義將不會在S<U>的隱式實例化時被實例化。只要您在看到U的定義後(或從S的其他成員函數的主體內使用bar,因爲這些函數反過來只會在需要時單獨實例化 - [14.6.4.1p1]),像這樣將工作:

template<class T> struct S 
{ 
    static constexpr int bar() { return T::foo; } 
}; 

struct U : S<U> { static constexpr int foo = 42; }; 

int main() 
{ 
    constexpr int b = U::bar(); 
    static_assert(b == 42, "oops"); 
} 

在通過的P0386R2進入工作草案(目前N4606),[class.static。數據]/3已經修改;相關部分現規定:

[...] An inline static data member may be defined in the class definition and may specify a brace-or-equal-initializer. If the member is declared with the constexpr specifier, it may be redeclared in namespace scope with no initializer (this usage is deprecated; see D.1). [...]

這是通過改變[basic.def] /2.3補充:

A declaration is a definition unless:
[...]

  • it declares a non-inline static data member in a class definition (9.2, 9.2.3),

[...]

所以,如果是內聯,這是一個定義(有或沒有初始化)。和[dcl.constexpr]/1說:

[...] A function or static data member declared with the constexpr specifier is implicitly an inline function or variable (7.1.6). [...]

這意味着bar的聲明現在是一個定義,並根據上一節它不是實例化的S<U>隱式實例化的報價;只有bar(不包括初始值設定項)的聲明在當時被實例化。

在這種情況下的變化是很好的總結在[depr.static_constexpr]在當前工作草案的例子:

struct A { 
    static constexpr int n = 5; // definition (declaration in C++ 2014) 
}; 

const int A::n; // redundant declaration (definition in C++ 2014) 

這使得GCC的行爲符合標準的在C++ 1Z模式。