2014-04-23 54 views
6

我想了解一些我遞過來的遞歸C++模板代碼,並且我遇到了一些奇怪的行爲。出於某種原因,編譯器似乎能夠在編譯時添加兩個值,但是必須在運行時留下左移。即使如此,只有在嘗試使用C++ 11進行構建時纔會出現問題。使用C++ 11時,C++遞歸模板的奇怪行爲

的代碼(我已經煮下來,以後你會看到)定義2對模板 - 一對名爲shftshft_aux和一個名爲addadd_aux產生自己遞歸對。順便說一句,add模板不應該是有用的,它的唯一目的是證明問題,而不是生成一個實際的min值。

如果我編譯這個沒有命令行參數的代碼,它編譯得很好。但是,如果我指定-std=c++11 -stdlib=libc++,add_aux上的static_assert仍然正常,但shft_aux上的static_assert現在會生成一個編譯時錯誤,說static_assert expression is not an integral constant expression

爲什麼左移與加法不同?

謝謝, 克里斯

附:我使用鐺++版本Apple LLVM version 5.1 (clang-503.0.38) (based on LLVM 3.4svn)

#include <climits> 

template <unsigned size> struct shft; // forward 

template <unsigned size> 
struct shft_aux 
{ 
    static const int min = shft<size>::min; 
}; 

template <unsigned size> 
struct shft 
{ 
    typedef shft_aux<size - 1> prev; 
    static const int min = prev::min << CHAR_BIT; 
}; 

// Base specialization of shft, puts an end to the recursion. 
template <> 
struct shft<1> 
{ 
    static const int min = SCHAR_MIN; 
}; 


// ----- 

template <unsigned size> struct add; // forward 

template <unsigned size> 
struct add_aux 
{ 
    static const int min = add<size>::min; 
}; 

template <unsigned size> 
struct add 
{ 
    typedef add_aux<size - 1> prev; 
    static const int min = prev::min + CHAR_BIT; 
}; 

// Base specialization of add, puts an end to the recursion. 
template <> 
struct add<1> 
{ 
    static const int min = SCHAR_MIN; 
}; 


// ----- 

int main() 
{ 
    static_assert(shft_aux<sizeof(int)>::min < 0, "min is not negative"); 
    static_assert(add_aux<sizeof(int)>::min < 0, "min is not negative"); 

    return 0; 
} 
+0

這就像那個老笑話......「醫生,當我這樣做的時候會很痛苦......」「所以不要這樣做!」既然我完全理解了這個問題,並有了標準的支持,我就有信心提出一種解決問題的不同方式,而不需要左移負數。 –

回答

7

C++ 11標準,[expr.shift]/2

E1 << E2值是E1左移E2位位置;空位被零填充。如果E1有一個無符號類型,[...]。否則,如果E1具有帶符號類型和非負值,並且 E1 * 2 E2在結果類型中可表示爲 ,則這是結果值; 否則,行爲是未定義的

[重點礦山]

這是通過DR1457這使得換檔進入「符號位」定義的行爲受到輕微的影響:

否則,如果E1具有簽名的類型和非負值,並且 E1 * 2 E2可以在結果類型[...]的相應的無符號類型中表示。

無論如何,在OP中,E1是負數,所以仍然是未定義的行爲。因此,它是不允許的內部常量表達式

[expr.const]/2 甲條件表達式芯常量表達式除非它涉及以下作爲一個潛在的一個評估子表達式[...]

  • [...]
  • 未數學上定義的或不在其類型表示的值的範圍內的結果;

即點已改變(DR1313);在n3485它說:

  • 將有不確定的操作[注操作:,包括例如,帶符號的整數過 流量(第5章),某些指針運算(5.7),除以零點(5.6)或某些移位操作(5.8) - 結束註釋];

[class.static.data]/3

如果非易失性const static數據成員是整型或枚舉類型的,其在類定義聲明可以指定一個括號或等號初始值設定項其中初始值子句這是一個賦值表達式是一個常量表達式


結論:移位SCHAR_MIN不是一個常量表達式,這樣就可以這樣做,在一個靜態數據成員的同類初始化。

提示:總是用-Wall -Wextra -pedantic進行編譯。不使用參數IMO對於g ++和兼容的編譯器來說是個壞主意。 g ++/clang ++默認使用gnu99模式(see clang doc),它是C++ 98 AFAIK的擴展。此外,你會錯過許多重要的警告。

+1

不知何故,我得到的印象是引用標準本身不是很有用。也許我應該堅持我的座右銘「n3485是C++ 11的最佳草案」。 – dyp

+0

非常感謝您引用標準!你是否因爲這樣做而得到一些負面反饋? –