krimchangのブログ

日々の発見を書き連ねるブログ

アンサイクロペディアのC++記事に有るHelloWorldは、C++11以降動かない

以下のソースコードです。

#include <iostream>

template<char O, bool =O == 'h'? '-' :O == 'w'? '=' :0>class _; 

template<char O>std::ostream&operator<<(std::ostream&lhs,_<O,0>*) {
    return lhs << O;
}

template<char O>std::ostream&operator<<(std::ostream&lhs, _<(O), 'o'>*) {
    return lhs<<(char)(O - '!' + '.' - '-' );
}

template<> std::ostream&operator<<<'~'>(std::ostream& m9,_<'~', 'o'>*) {
    return m9<<('m');
}

int main(int orz=3) {
    std::cout<<(_<'h'>*)0<<(_<'e'>*)0<<(_<'l'>*)0<<(_<'l'>*)0<<(_<'o'>*)0<<(_<'w',0>*)0<<(_<' '>*)0<<(_<'w'>*)0<<(_<'o'>*)0<<(_<'r'>*)0<<(_<'l'>*)0<<(_<'d'>*)0<<(_<'!'>*)0<<std::endl;
    return 0;
}

引用元(アンサイクロペディア C++)

C++11以降、このソースはコンパイルエラーとなります。

なぜ

結論から先に記載してしまうと

  1. 非型テンプレート引数は定数式(constant expression)であり
  2. 定数式は縮小変換(narrowing conversion)を行うことが出来ないため。

となります。

非型テンプレート引数(typename Tやclass Tでない)は
C++規格で言う定数式(constant expression)であり

定数式は縮小変換(narrowing conversion)を行うことが出来ません。

確認

エラーになる箇所を確認してみます。

template<char O, bool =O == 'h'? '-' :O == 'w'? '=' :0>class _; 

char Oが'h'または'w'の時
bool = '-' または '='として展開されますが。

'-'や'='は1を超過しているため(1 < '-')
bool代入は縮小変換に該当してしまい、エラーになります。

以下のソースコードも 同じ理由でエラーです。

template<char O>std::ostream&operator<<(std::ostream&lhs, _<(O), 'o'>*) {
    return lhs<<(char)(O - '!' + '.' - '-' );
}

template<> std::ostream&operator<<<'~'>(std::ostream& m9,_<'~', 'o'>*) {
    return m9<<('m');
}

第2テンプレート引数に'o'が渡っており
上記と同じ縮小変換に該当してしまいます。

解決方法

解決方法を幾つか考えてみましたが
正直言ってイマイチです。

class _のデフォルト引数を0or1にするとか

template<char O, bool =O == 'h'? 1 :O == 'w'? 1 :0>class _{}; 

引数指定部では!=を挟むとか

template<char O>std::ostream&operator<<(std::ostream&lhs, _<(O), 'o' != 0>*) { // != 0を挟む
    return lhs<<(char)(O - '!' + '.' - '-' );
}

明示キャストを挟むとか

template<> std::ostream&operator<<<'~'>(std::ostream& m9,_<'~', static_cast<bool>('o')>*) { // 定数式変換前に明示的キャストを行う
    return m9<<('m');
}

うーん

#include <iostream>

template<char O, bool =O == 'h'? 1 :O == 'w'? 1 :0>class _{}; 

template<char O>std::ostream&operator<<(std::ostream&lhs,_<O,0>*) {
    return lhs << O;
}

template<char O>std::ostream&operator<<(std::ostream&lhs, _<(O), 'o' != 0>*) { // != 0を挟む
    return lhs<<(char)(O - '!' + '.' - '-' );
}

template<> std::ostream&operator<<<'~'>(std::ostream& m9,_<'~', static_cast<bool>('o')>*) { // 定数式変換前に明示的キャストを行う
    return m9<<('m');
}

int main(int orz=3) {
    std::cout<<(_<'h'>*)0<<(_<'e'>*)0<<(_<'l'>*)0<<(_<'l'>*)0<<(_<'o'>*)0<<(_<'w',0>*)0<<(_<' '>*)0<<(_<'w'>*)0<<(_<'o'>*)0<<(_<'r'>*)0<<(_<'l'>*)0<<(_<'d'>*)0<<(_<'!'>*)0<<std::endl;
    return 0;
}

問題を解決したは良いものの
元記事にあるユーモア性が失われてしまいました。

ユーモアのあるソースコードを書くのは難しいですね

参考

超参考にしたstackoverflow質問。10割答え

Narrowing int to bool in SFINAE, different output between gcc and clang

また、上の質問には書かれていませんが。
仕様書には、定数式での文脈上bool変換(contextually converted to bool)は
[expr.const]に記載された変換しか受け付けてくれない

とご丁寧に記載されています。
(英検100級なので翻訳に自身がない)

[expr.const] A contextually converted constant expression of type bool is an expression, contextually converted to bool, where the converted expression is a constant expression and the conversion sequence contains only the conversions above. https://eel.is/c++draft/expr.const

しかし、普通に考えて
何もbool変換まで、縮小変換NGにする必要は無かったのではと
考えざるを得ません。

この辺りの経緯や意図については
今後の宿題ですね。