2014年4月29日火曜日

C++14の関数戻り値型推論をC++11で模擬【可変長引数マクロ】

C++14で導入予定の関数戻り値型推論

参考:http://faithandbrave.hateblo.jp/entry/20130501/1367396895

C++11でも楽したい

Webを眺めていると、C++14がすでに標準の世界であるかのようにも見えてしまう。 しかし現在の標準はやはりC++11だし、多くの人はまだC++03を書いている。気がする。

C++03に比べれば、C++11では快適にコーディングできる。断然楽だ。C++14は、もっと楽そうだ。

戻り値型の指定

C++で普段からinlineな関数ばかり書いていると、戻り値型の型指定がとてもしんどい。 typedef構文なしでは生きていけないC++03は、もはや使うことも難しい(個人の感想です)。

C++14ではラムダ式のように、一般関数にも戻り値型推論の機能が備わるとのことで待ち遠しい。 茶を飲みながら待つにはまだ少々長い時間がかかりそうなので、C++11の枠内でなんとかならないか考えてみた。

// http://melpon.org/wandbox/permlink/jN0QetajU1VFBP5f
#include<iterator>

template<typename T>
inline auto size(T const &x)
{
  return std::distance(std::begin(x), std::end(x));
}

#include<iostream>
#include<vector>

auto main()->int
{
  auto x0{1, 2, 3, 4, 5}; // std::initializer_list
  auto x1 = std::vector<int>{1, 2, 3, 4, 5}; // std::vector
  int x2[5] {0}; // C-style array
  std::cout << size(x0) << std::endl;
  std::cout << size(x1) << std::endl;
  std::cout << size(x2) << std::endl;
}
// http://melpon.org/wandbox/permlink/FUg7M47wvyVKfumt
#include<iterator>

template<typename T>
inline auto size(T const &x)
->decltype(std::distance(std::begin(x), std::end(x))) // つらい
{
  return std::distance(std::begin(x), std::end(x));
}

#include<iostream>
#include<vector>

auto main()->int
{
  auto x0{1, 2, 3, 4, 5}; // std::initializer_list
  auto x1 = std::vector<int>{1, 2, 3, 4, 5}; // std::vector
  int x2[5] {0}; // C-style array
  std::cout << size(x0) << std::endl;
  std::cout << size(x1) << std::endl;
  std::cout << size(x2) << std::endl;
}

C++11で関数戻り値型推論を模擬

禁断の呪術「マクロ」による解決

ご存知の通り、C++の世界ではマクロは嫌厭される。私も嫌いだ。 しかし、他に手段がないときには使えばよい。

上の例を見ると、重複した記述がある。これをマクロでまとめてみる。

// http://melpon.org/wandbox/permlink/aBRRRWqU2cwfX9Yn
#define INLINE_RETURN(x)  ->decltype(x){return(x);}

#include<iterator>

template<typename T>
inline auto size(T const &x)
INLINE_RETURN(std::distance(std::begin(x), std::end(x)))

#include<iostream>
#include<vector>

auto main()->int
{
  auto x0{1, 2, 3, 4, 5}; // std::initializer_list
  auto x1 = std::vector<int>{1, 2, 3, 4, 5}; // std::vector
  int x2[5] {0}; // C-style array
  std::cout << size(x0) << std::endl;
  std::cout << size(x1) << std::endl;
  std::cout << size(x2) << std::endl;
}

うむ。まとまった。欠点をあげるとすると、これではreturn文以外の記述ができない。 なんとなくconstexpr関数っぽい制限。別の話ではあるが、C++14ではconstexpr関数の制限も緩くなるらしい。

なんとか複数文を実行できないか。そうだ、カンマ演算子を使えばある程度書けそうだ。

// http://melpon.org/wandbox/permlink/wFiOD4lGZ4EtBeX9
#define INLINE_RETURN(x)  ->decltype(x){return(x);}

#include<iostream>
#include<iterator>

template<typename T>
inline auto size(T const &x)
INLINE_RETURN
( std::cout << "size()" << std::endl
, std::distance(std::begin(x), std::end(x))
)

#include<vector>

auto main()->int
{
  auto x0{1, 2, 3, 4, 5}; // std::initializer_list
  auto x1 = std::vector<int>{1, 2, 3, 4, 5}; // std::vector
  int x2[5] {0}; // C-style array
  std::cout << size(x0) << std::endl;
  std::cout << size(x1) << std::endl;
  std::cout << size(x2) << std::endl;
}
prog.cc:10:3: error: too many arguments provided to function-like macro invocation
, std::distance(std::begin(x), std::end(x))
  ^

clangさんマジ親切。MSVCで始めて遭遇した時は???だった。 要するに、カンマ演算子のつもりで書いた「,」は、見事マクロ引数の区切りとみなされたわけである。 これを回避するには更に丸括弧で括ることも考えられるが、既に丸括弧が連続していてつらさが高まっている。

高等魔法「可変長引数マクロ」による拡張

C99に導入されて、C++11にもサポートされた可変長引数マクロ (Variadic Macro)という機能がある。 マクロはまだまだ消えそうにない。ところでVariadicという語は造語らしい (http://cpplover.blogspot.jp/2010/02/variadic.html)。目からウロコ。

さて、お気づきだろうが、カンマがコンパイラにマクロ引数の区切りと認識されてしまったなら、 本当にマクロ引数の区切りとしてしまえばいいだろう。 そしてマクロの展開時にカンマ演算子となるようにすれば解決だ。

// http://melpon.org/wandbox/permlink/3Z4T5pfyoOdVu3lI
#define INLINE_RETURN(...) \
->decltype(__VA_ARGS__){return(__VA_ARGS__);}

#include<iostream>
#include<iterator>

template<typename T>
inline auto size(T const &x)
INLINE_RETURN
( std::cout << "size()" << std::endl
, std::distance(std::begin(x), std::end(x))
)

#include<vector>

auto main()->int
{
  auto x0{1, 2, 3, 4, 5}; // std::initializer_list
  auto x1 = std::vector<int>{1, 2, 3, 4, 5}; // std::vector
  int x2[5] {0}; // C-style array
  std::cout << size(x0) << std::endl;
  std::cout << size(x1) << std::endl;
  std::cout << size(x2) << std::endl;
}

いろいろ制限はあるものの、ワンライナーな関数実装には非常に効果的だ。 最近では多用している。

At Your Own Risk

このマクロ作成にあたって、類似テクニックを調査していない。また、第三者による検証も全くない。 どこかに類似の手法やより良い代替があったら紹介してほしい。まずいところがあったら教えてほしい。 使用するときは、よくよく注意してほしい。

0 件のコメント:

コメントを投稿