C++ で関数引数の評価順序を保証したかった
[関数引数の評価順序]
C++では関数を呼び出す際に渡される引数の評価順序は未規定になっています。
どういうことかというと次のようなコードの場合、処理系によって出力される値が異なる可能性があります。
template<typename T> auto print(T value){ std::cout << value; return value; } template<typename... Args> void func(Args&&...){} // 123 と出力されるかもしれないし 321 と出力されるかもしれない func(print(1), print(2), print(3));
上記のようなコードの場合、 clang では 123
と出力されますが、gcc では 321
と出力されます。
ただし、これは関数呼び出しの場合のみで、コンストラクタの {}
呼び出しや、初期化リストの {}
では順序が『左から右の順で評価される』ことが保証されています。
ですので次のような初期化リストを呼び出した場合は期待通りの動作を行います。
template<typename T> auto print(T value){ std::cout << value; return value; } // 123 と出力されることが保証されている auto dummy = { print(1), print(2), print(3) };
[可変長テンプレート引数で引数分関数を呼び出したい]
上記の仕様を踏まえた上で、次のような処理を考えてみます。
template<typename F, typename... Args> auto call(F f, Args... args){ // 可変長引数の数だけ f を呼び出してその結果を tuple で返す return std::make_tuple(f(args)...); } using namespace std::literals::string_literals; auto twice = [](auto a){ return a + a; }; auto result = call(twice, 1, 3.5, "homu"s); assert(result == std::make_tuple(2, 7.0, "homuhomu"));
『可変長引数の数だけ f
を呼び出してその結果を tuple
として返す』という簡単な関数です。
しかし、残念なことに std::make_tuple(f(args)...)
という関数呼び出しでは、評価順序が処理系によって異なってしまいます。
auto print = [](auto a){ std::cout << a << std::endl; return a; }; // print の呼び出しが不規定なのでどういう出力になるのかが保証されてない call(print, 1, 3.5, "homu"s);
[std::make_tuple
ではなくて std::tuple
コンストラクタを使う]
上記の問題は std::make_tuple
ではなくて std::tuple
のコンストラクタを呼び出すことで回避できます。
template<typename F, typename... Args> auto call(F f, Args... args){ // std::tuple のコンストラクタを呼び出すことで評価順序を保証する return std::tuple<decltype(f(args))...>{f(args)...}; } auto print = [](auto a){ std::cout << a << std::endl; return a; }; // 出力結果が保証される call(print, 1, 3.5, "homu"s);
これで意図した順番で評価される事が保証されます。
[参照]
C++ の可変引数リストに non trivial な値を渡すとコンパイルエラーになる
以下のようなコードを Clang でコンパイルしたところエラーになりました。
[](...){}(std::string("homu")); // error: cannot pass object of non-trivial type 'std::string' (aka 'basic_string<char, char_traits<char>, allocator<char> >') through variadic method; call will abort at runtime [-Wnon-pod-varargs]
gcc だと問題なかったのでなんでだろーと思って調べていたらどうやら未定義の動作みたい。
参照:可変引数リストと非PODクラス型の関係 - yohhoyの日記
件のコードだとラムダ式ですが、通常の関数定義も同様です。
ちなみに variadic template として受け取れば回避出来ました。
[](auto...){}(std::string("homu")); // ok
C++ きびしい…。
C++17 の std::optional に無効値を設定する
C++17 では Boost.Optional が新しく標準ライブラリ入りしました。
Boost.Optional で無効値を指定する場合は boost::none
を使っていたんですが、標準ライブラリの std::optional
では std::nullopt
を使用して無効値を指定します。
[ソースコード]
#include <optional> #include <iostream> template<typename T> void print(std::optional<T> const& op){ if( op ){ std::cout << "OK:" << * op << std::endl; } else{ std::cout << "NG" << std::endl; } } int main(){ std::optional<int> value; print(value); value = 42; print(value); value = std::nullopt; print(value); return 0;; }
[出力]
NG OK:42 NG
nullptr
と nullopt
が似ていてややこしい。。。
void でキャストすることで "unused" の警告を回避する
元々は C言語?で使われているようなハックらしいのですが、知らなかったので覚書。
さて、例えば次のように関数の引数を assert
チェックしてる関数があるとします。
void func(int value){ assert(value == 42); }
この場合、デバッグビルドでは問題ないんですがリリースビルドでは『assert
で参照してる value
が使用されていない』とコンパイラが判断して、
warning: unused parameter 'value' [-Wunused-parameter]
という風な警告を出す可能性があります。
このような場合、次のように void
でキャストして回避することが可能です。
void func(int value){ (void)value; assert(value == 42); }
たまに (void)xxx;
しているコードを見かける事があるんですが、こういう理由だったんですね…。
ちなみに C++17 では [[maybe_unused]]
を使用して回避することが出来ます。
void // コンパイラに対して "使用されない可能性がある変数" として明示化する func([[maybe_unused]] int value){ assert(value == 42); }
[参照]
Ruby で任意の異なるクラスのオブジェクトを #uniq するときの注意
元ネタ
Rubyむずい…うむむむむむむむうむ
— 異常者 (@h1manoa) 2017年6月8日
[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ https://t.co/buooxLKDHW
と、いうことでちょっとやってみたら見事にハマったので覚書
元のコード
class Hoge def initialize(num) @num = num end def ==(other) if @num == other true else false end end end p [Hoge.new(1), Hoge.new(2), 1, 2, 5 ,6].uniq # => [#<Hoge:0x000000024871f8 @num=1>, #<Hoge:0x000000024871d0 @num=2>, 1, 2, 5, 6]
元のコードはユーザ定義クラスと Integer
が入り混じった配列をいい感じに #uniq
したいという内容でした。
#uniq
の条件
#uniq
は要素の Object#eql?
を使用して重複を判定します。
また、 #eql?
を定義した場合は Object#hash
も再定義する必要があります。
と、いうことでこれに沿って修正してみました。
class Hoge def initialize(num) @num = num end def hash @num.hash end def eql?(other) if @num == other true else false end end end p [Hoge.new(1), Hoge.new(2), 1, 2, 5 ,6].uniq # => [#<Hoge:0x000000024871f8 @num=1>, #<Hoge:0x000000024871d0 @num=2>, 1, 2, 5, 6]
しかし、これでもまだ意図した動作になりません。
#hash
と #eql?
は双方向で true
になる必要がある
調べていたらわかったんですが
Hoge.new(1).eql? 1 # => true
だけではなくて
1.eql? Hoge.new(1).eql? # => true
のように双方向で true
になる必要があるみたいです(当然 #hash
に関しても同様
完成
と、言うことで Integer#eql?
を拡張することで無事に意図する動作になりました。
class Hoge def initialize(num) @num = num end def hash @num.hash end def eql?(other) if @num == other true else false end end end class Integer def eql?(other) return super(other) unless Hoge === other other.eql? self end end _1 = Hoge.new(1) # この条件をすべて満たす必要がある p _1.hash == 1.hash p _1.eql? 1 p 1.hash == _1.hash p 1.eql? _1 p [Hoge.new(1), Hoge.new(2), 1, 2, 5 ,6].uniq # => [#<Hoge:0x000000024871f8 @num=1>, #<Hoge:0x000000024871d0 @num=2>, 5, 6]
流石に #uniq
のためだけに Integer
をクラス拡張するのはツラいですね…。
まとめ
#uniq
は#eql?
を参照して重複のチェックを行う#eql?
を書き換えた場合は#hash
も書き換える必要がある#uniq
は双方向でチェックするので要素すべてのオブジェクトに対して#eql?
を考慮する必要がある
RSpec で pending しても before :context が呼ばれてしまう
結果だけ先に書いてしまいますが『pending しているスコープで before :context
を定義すると before
のブロックが呼ばれます』。
どういうことかというと次のように before :context
内で例外が発生した場合、テストが失敗してしまいます。
describe X do # 以下のブロックを pending にする xcontext 'hoge' do # pending しているのにもかかわらず it 時に before のブロックが呼ばれてしまう before :context do raise "error" end it { expect(0).to eq 0 } end end
これは pending してるのにもかかわらず before
に :context
を指定した場合、it
時にブロック内の処理が呼ばれてしまうためです。
また、この問題(というべきかはわかりませんが)は :context
を渡した時のみ発生し、引数を渡さなかったり :example
を渡した場合には再現しません。
ちなみに :all
は :context
のエイリアスなので :all
を渡しても同様の問題が発生します。
どういう時に困るのか
例えば、次のように before
内で実装する予定のメソッドを呼び出していると例外が発生するのでテストが失敗してしまいます。
describe X do # X.hoge を後で実装する xcontext '.hoge' do before :context do # この段階では .hoge は未実装 @result = X.hoge end it { expect(@result).to eq "hoge" } it { expect(@result).to be_kind_of String } end end
こういう場合は before :context
を使わなかったり、before
以外(subject
など)を使用して回避する必要があります。
[関連]
https://github.com/rspec/rspec-core/issues/107
[追記]
書き忘れてましたが RSpec のバージョンは 3.6.0 です。
Ruby の Method#=== が定義されていないのでつらい問題
Method#===
が定義されていなくてつらい問題です。
Proc#===
Proc
オブジェクトでは #call
で評価することが出来ますが、そのエイリアスとして #===
が存在します。
upcase = proc { |a| a.upcase } upcase.call "homu" # => "HOMU" upcase === "mami" # => "MAMI"
これは次のように when
で使用出来るようにするためです。
def sign n case n when proc { |n| 0 < n } 1 when proc { |n| 0 > n } -1 else 0 end end sign -4 # => -1 sign 0 # => 0 sign 7 # => 1
ここで、Ruby に精通してる人なら気づくと思いますが、#method
を使って proc { |n| 0 < n }
を 0.method(:<)
と書き直したくなります。
def sign n case n # #method はレシーバのメソッドをブロックオブジェクトとして返す when 0.method(:<) 1 when 0.method(:>) -1 else 0 end end
しかし、これは上手く動作しません。
なぜなら #method
が返す Method
クラスでは #===
が定義されていないからです。
Method#===
を定義する gem をつくった
と、いうことで Method#===
を使うことが出来る gem をつくりました。
$ gem install procedureable_method
で、インストールすることが出来ます。
使い方
使い方は
require "procedureable_method" using ProcedureableMethod
という風に refinements として使用するか
require "procedureable_method/core_ext"
として直接モンキーパッチを適用されるかします。
これで Method#===
を使用する事が出来ます。
require "procedureable_method" using ProcedureableMethod def sign n case n when 0.method(:<) 1 when 0.method(:>) -1 else 0 end end sign -4 # => -1 sign 0 # => 0 sign 7 # => 1
これで無事に when
に Method
を渡して評価する事が出来ました。
関係ないですが when
の場合はちゃんと refinements で定義したメソッドが呼ばれるんですね。
本体にも Method#===
が追加されてほしい。