割と今まで読んできた本を聞かれることが多いので、ちょっと思い出そうとしてみる。記憶から漏れているものもあるかもしれないので思い出したら足すかも知れない。
私はC++の前にCを少しだけやっていたので、記憶はそこから始まる。
続きを読むこんなツイートを見た。
#Thursdaysurvey on this code:
— Meeting C++ (@meetingcpp) September 14, 2017
> variant<string, int, bool> v = "Hello!"; <
What type is contained in v?#cpp#cplusplus
答えはbool
だ。その理由は、こういう一見自明なクイズは大抵一番正しそうに見える解答が誤答だから、ではなく、暗黙の型変換の優先準位によるものだ。
まず、"Hello!"
の型はstd::string
ではない。これは文字列リテラルなので、その型はconst char[]
だ。上記のコードにおいては、これがまず先頭を指すポインタ(const char*
)へ変換され、ポインタがbool
へ変換される。std::string
は標準ライブラリといえどユーザー定義型であることは間違いないので、built-inな型のほうが選択され、これはbool
になる。
いや、確かに、この挙動はクソだ。100人に聞いたら100人が、これはstd::string
になるべきだと答えるだろう。私もそう思う。この挙動はライブラリがどうにかできるものでもないし、ユーザー定義型への変換を優先する方が問題が生じる場合もありそうで、仕方ないと言えば仕方ない気もするが……。
挙動が直感的でないのに対して、解決策は単純だ。文字列リテラルからstd::string
への暗黙の型変換に頼らず、型を明示すればよい。つまり、std::string_literals::operator""s
を使えばよい。
using std::string_literals::operator""s; std::variant<std::string, int, bool> v = "Hello!"s;
これなら型はstd::string
になる。
以前、あるプロジェクトが使っているクラスがbool
への型変換関数を持っていることを知らず、そのクラスがbool
へ変換された後にint
に変換されることでオーバーロードが謎の方向に解決されるという事案が発生して死んでいたことがある。今回の件ではそれを思い出した。
コンテナが空(要素を持っていない)かどうかを確認するとき、タイトルの2つのメソッドのどちらを使うかという話題で界隈が盛り上がっていたようだ。
私はempty()
一択だ。サイズが0かどうかを確認したいなら、size() == 0
を使うべきだ。だが、コンテナが空であることを確認できるメソッドがある場合に、コンテナが空であるかを確認するために別のコードを書く理由がない。
その2つが異なる場合があるのか、というのは疑わしい。ただ、未初期化領域を区別できるようなコンテナを作って、過去のSTLの命名を全て無視して関数を命名したなら、場合によっては筋が通った形でempty
かつsize != 0
という結果になることはありえるだろう。size
はnon-zeroだが、確保してある全領域が未初期化のままなのでempty
、というように。
C++では実際には、リストのようにsize
を取得するコストが定数でない可能性のあるデータ構造を考えてempty
が作られたのだろうと思われるが、list::size
が定数時間とされてからはこの点に関してはどうでもよくなった。
問題があるとすれば、empty
の命名だろうか。C++界隈では人々は長年STLに親しんでいるのでこの名前の関数がどう機能するかについて合意しあっているが、そうでない人がこれを見た時にどう反応するかはわからない。
もしこれが、is_empty
やempty?
のような命名であれば、オブジェクトの状態を取得し、bool
を返すというところまで含意できたのだが。
というわけで以下のようにしよう。
#define is . std::vector<int> vec; std::cout << std::boolalpha << vec is empty() << std::endl;
言うまでもないがこのコードは冗談だ。
私の書くプログラムでは、趣味でも仕事でも、低次元(大抵は2か3次元)でのベクトルを扱うことが多い。そうなると普通は座標ベクトルを意味する構造体を作って管理するだろう。
続きを読む会話をしていると、FizzBuzzの話になった。何かの拍子にFizzBuzzを書いてくださいと言われたらどうするだろうか、と思ってちょっと書いてみることにした。たまには気楽に書けるものもよい。
FizzBuzzを自然に捉えると、数字を文字列に変換していくルールに従って、入ってくる数字の列を変換し順に出力するというものだろう。
ということは、iota
的なイテレータをtransform_iterator
で覆って、ostream_iterator
に投げ込んでいけばいい。
iota
的なことをしてくれるイテレータはないかと探したところ(作れるが演算子定義などが面倒だ)、boost::iterator::counting_iterator
がよいとわかった。
#include <boost/iterator/transform_iterator.hpp> #include <boost/iterator/counting_iterator.hpp> #include <algorithm> #include <iostream> #include <string> int main(int argc, char** argv) { if(argc != 2) { std::cerr << "usage: ./fizzbuzz <maximum>" << std::endl; return 1; } const auto fizzbuzz = [](const std::uint64_t i){ using namespace std::literals::string_literals; if(i%3 == 0 && i%5 == 0) return "FizzBuzz"s; else if(i%3 == 0) return "Fizz"s; else if(i%5 == 0) return "Buzz"s; else return std::to_string(i); }; std::copy( boost::make_transform_iterator( boost::make_counting_iterator(1ull), fizzbuzz), boost::make_transform_iterator( boost::make_counting_iterator( std::stoull(argv[1]) + 1ull), fizzbuzz), std::ostream_iterator<std::string>(std::cout, ", ")); std::cout << std::endl; return 0; }
こんなもんだろう。しかしまあ、1からmaxまでの数字の列を作って、それに関数を適用するだけだというのに非常に面倒なことをしないといけないのはやはり辛い。これくらい簡単になってもよいと思うのだが。
#include <algorithm> #include <iostream> #include <string> int main(int argc, char** argv) { if(argc != 2) { std::cerr << "usage: ./fizzbuzz <maximum>" << std::endl; return 1; } const auto fizzbuzz = [](const std::uint64_t i){ if(i%3 == 0 && i%5 == 0) return "FizzBuzz"s; else if(i%3 == 0) return "Fizz"s; else if(i%5 == 0) return "Buzz"s; else return std::to_string(i); }; const auto max = std::stoull(argv[1]); (1ull ... max).transform(fizzbuzz).output(std::cout, ", "); return 0; }
と、思った辺りでBoost.rangeの存在を思い出した。
#include <boost/range/algorithm.hpp> #include <boost/range/irange.hpp> #include <boost/range/adaptor/transformed.hpp> #include <iostream> #include <cstdint> int main(int argc, char** argv) { if(argc != 2) { std::cerr << "usage: ./fizzbuzz <maximum>" << std::endl; return 1; } const auto fizzbuzz = [](const std::uint64_t i){ using namespace std::literals::string_literals; if(i%3 == 0 && i%5 == 0) return "FizzBuzz"s; else if(i%3 == 0) return "Fizz"s; else if(i%5 == 0) return "Buzz"s; else return std::to_string(i); }; boost::copy(boost::irange(1ull, std::stoull(argv[1]) + 1ull) | boost::adaptors::transformed(fizzbuzz), std::ostream_iterator<std::string>(std::cout, ", ")); std::cout << std::endl; return 0; }
やはりこうするとかなりマシだ。