2017-09-18 11 views
16

雖然我用這樣的代碼之前,並且很顯然,編譯器有足夠的信息來工作,我真的不明白爲什麼這個編譯:積分常數按價值傳遞,視爲constexpr?

template <class T, class I> 
auto foo(const T& t, I i) { 
    return std::get<i>(t); 
} 

int main() 
{ 
    std::cerr << foo(std::make_tuple(3,4), std::integral_constant<std::size_t, 0>{}); 
    return 0; 
} 

活生生的例子:http://coliru.stacked-crooked.com/a/fc9cc6b954912bc5

似乎與gcc和clang一起工作。問題是,儘管integral_constantconstexpr轉換爲存儲的整數,但constexpr成員函數隱式地將對象本身作爲參數,因此,除非我們正在調用成員函數本身的對象,否則這樣的函數不能用於constexpr上下文中可以視爲constexpr

這裏,i是傳遞給foo的一個參數,因此i肯定不能被當作constexpr。然而,它是。一個更簡單的例子:

template <class I> 
void foo(I i) { 
    constexpr std::size_t j = i; 
} 

這編譯過,只要std::integral_constant<std::size_t, 0>{}傳遞給foo

我覺得我失去了明顯的constexpr規則。是否有無狀態類型的例外或其他? (或者,也許是兩個主要編譯器中的編譯器錯誤?這段代碼似乎適用於clang 5和gcc 7.2)。

編輯:答案已發佈,但我認爲它不夠。特別是,鑑於foo最後一個定義,爲什麼:

foo(std::integral_constant<std::size_t, 0>{}); 

編譯,而不是:

foo(0); 

均爲0,且std::integral_constant<std::size_t, 0>{}是常量表達式。

編輯2:它似乎歸結爲事實上,即使在不是常量表達式的對象上調用constexpr成員函數,本身也可以被視爲常量表達式,只要this未被使用。這很明顯。我不認爲這是很明顯的:

constexpr int foo(int x, int y) { return x; } 

constexpr void bar(int y) { constexpr auto x = foo(0, y); } 

這並不編譯,因爲y作爲傳遞到foo不是一個常量表達式。它沒有被使用並不重要。因此,只要this未使用,完整的答案需要顯示標準中的某種語言,以證明成員函數可用作常量表達式,即使在非常量表達式對象上也是如此。

+0

也適用於Visual C++:https://godbolt.org/g/Cknxco – Justin

+0

似乎在英特爾編譯器上失敗:https://godbolt.org/g/wLc5dw – Justin

+2

@Justin好趕上,雖然它是icc我打賭這是另一個無關的錯誤:-)。 –

回答

17

編譯時常量表達式的規則隨着constexpr,LOT而改變,但它們並不是新的。在constexpr之前,已經有編譯時常量表達式......並且舊規則在新規範中作爲特例保留下來,以避免破壞大量現有代碼。

在大多數情況下,舊規則處理整型的編譯時常量(又名積分常量表達式 ......這正是您正在處理的情況。所以不,在constexpr規則中沒有什麼奇怪的...這是其他與constexpr無關的老規則。

甲條件表達式e是一個核心常量表達式除非e的評價,如下所述抽象機的規則,將評估下列表達式之一:

...

  • 左值到右值轉換,除非它被施加到
    • 整型或枚舉類型的非易失性glvalue引用完全非揮發性const對象與prece丁初始化,初始化爲常量表達式或
    • 非易失性glvalue引用字符串文字的子對象,或
    • 非易失性glvalue其指的是與constexpr定義非易失性對象德網絡連接,或是指這樣一個對象的不可變子對象,或者是一個非易失性的文字類型的glvalue,它指的是一個非易失性對象,其生命週期始於e的評估;

你說得對,第三subbullet不適用。但是第一個呢。

因此,在允許函數返回爲編譯時常量的新規則之間存在一種有趣的相互作用,具體取決於抽象機器上的評估規則,以及允許整型值爲編譯時常量的遺留行爲,即使沒有標記爲這樣。


這裏有一個簡單的例子,爲什麼它不要緊,this是一個隱含參數。作爲一個參數,並不意味着對象進行評估:

constexpr int blorg(bool const flag, int const& input) 
{ 
    return flag? 42: input; 
} 

int i = 5; // 5 is an integral constant expression, but `i` is not 
constexpr int x = blorg(true, i); // ok, `i` was an argument but never evaluated 
constexpr int y = blorg(false, i); // no way 

對於std::integral_constant成員函數,你可以在blorg功能考慮*thisi - 如果執行不取消對它的引用,這是確定它是在沒有編譯時常量的情況下傳遞。

+0

我想了一下這個答案,對我來說似乎如果這個答案是正確的,我的推理就是我更新的問題中的'foo(0)'也應該編譯,但它不會。我認爲完全理解編輯中兩個電話的區別將清楚地回答這個問題。 –

+0

我認爲這些都不適用,因爲沒有左值到右值的轉換。 – cpplearner

+0

@NirFriedman:在工作的情況下,唯一評估的是一個整體模板參數,它落入子彈1.在新的中斷代碼中,計算一個運行時變量參數。調用非虛擬成員函數並不一定會評估調用它的表達式...'std :: integral_constant'轉換操作符不會訪問'this'的任何非靜態數據成員,因此不需要在不斷的情況下變得非法。 –

12

這部作品的原因:

template <class T, class I> 
auto foo(const T& t, I i) { 
    return std::get<i>(t); 
} 

是因爲沒有理由認爲它會失敗適用。當istd::integral_constant<size_t, S>,i時,i可用作size_t類型的轉換常數表達式,因爲該表達式通過constexpr operator size_t(),該表達式僅將模板參數(即prvalue)作爲值返回。這是一個非常有效的常數表達式。請注意,this未被引用 - 僅僅因爲它是一個成員函數本身並不違反常量表達式約束。

基本上,這裏沒有關於i的任何「runtimey」。

在另一面,如果i是一個int(由foo(0)方式),然後調用std::get<i>將涉及一個左到右值轉換爲i,但這種情況下將無法滿足any of the criteria因爲i沒有前面的初始化用一個常量表達式,它不是一個字符串文字,它沒有用constexpr定義,它並沒有在這個表達式中開始它的生命週期。

+0

對,重要的是,該值是從模板參數中提取的,而不是函數參數。 –

+0

[一個非類型的非參考模板參數是一個prvalue。](http://eel.is/c++draft/temp.param#6.sentence-1) – cpplearner

+0

@cpplearner哦,對,總是忘記。乾杯,固定 – Barry