プリプロセッサでFOR_EACHを実現する

[プリプロセッサ, 再帰]などで検索して来られる方が居ますがCプリプロセッサ再帰は出来ません。 プリプロセッサメタプログラミングにおける要素の走査は、再帰っぽく見えるだけで実際にはただの連番マクロを用いた展開によって実装されています。

少し実用的な可変長引数を順番に展開する例を掲載しておきます。

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

#define CAT_I(x, y) x ## y
#define CAT(x, y) CAT_I(x, y)

#define INC0 1
#define INC1 2
#define INC2 3
#define INC3 4
#define INC4 5
#define INC5 6
#define INC6 7
#define INC7 8
#define INC8 9
#define INC(i) CAT(INC, i)

#define EMPTY(...)
#define DEF_COMMA0 _,1 EMPTY
#define COMMA0() ,0
#define IS_EMPTY_III(f, s) s
#define IS_EMPTY_II(t) IS_EMPTY_III t
#define IS_EMPTY_I(x) IS_EMPTY_II((DEF_ ## x()))
#define IS_EMPTY(x, ...) IS_EMPTY_I(x COMMA0)

#define IF_0(x, y) y
#define IF_1(x, y) x
#define IF(cond, x, y) CAT(IF_, cond)(x, y)

#define FOR_EACH_I9(i, F, x, ...) F(x) IF(IS_EMPTY(__VA_ARGS__), EMPTY, CAT(FOR_EACH_I, i))(INC(i), F, __VA_ARGS__)
#define FOR_EACH_I8(i, F, x, ...) F(x) IF(IS_EMPTY(__VA_ARGS__), EMPTY, CAT(FOR_EACH_I, i))(INC(i), F, __VA_ARGS__)
#define FOR_EACH_I7(i, F, x, ...) F(x) IF(IS_EMPTY(__VA_ARGS__), EMPTY, CAT(FOR_EACH_I, i))(INC(i), F, __VA_ARGS__)
#define FOR_EACH_I6(i, F, x, ...) F(x) IF(IS_EMPTY(__VA_ARGS__), EMPTY, CAT(FOR_EACH_I, i))(INC(i), F, __VA_ARGS__)
#define FOR_EACH_I5(i, F, x, ...) F(x) IF(IS_EMPTY(__VA_ARGS__), EMPTY, CAT(FOR_EACH_I, i))(INC(i), F, __VA_ARGS__)
#define FOR_EACH_I4(i, F, x, ...) F(x) IF(IS_EMPTY(__VA_ARGS__), EMPTY, CAT(FOR_EACH_I, i))(INC(i), F, __VA_ARGS__)
#define FOR_EACH_I3(i, F, x, ...) F(x) IF(IS_EMPTY(__VA_ARGS__), EMPTY, CAT(FOR_EACH_I, i))(INC(i), F, __VA_ARGS__)
#define FOR_EACH_I2(i, F, x, ...) F(x) IF(IS_EMPTY(__VA_ARGS__), EMPTY, CAT(FOR_EACH_I, i))(INC(i), F, __VA_ARGS__)
#define FOR_EACH_I1(i, F, x, ...) F(x) IF(IS_EMPTY(__VA_ARGS__), EMPTY, CAT(FOR_EACH_I, i))(INC(i), F, __VA_ARGS__)
#define FOR_EACH_I0(i, F, x, ...) F(x) IF(IS_EMPTY(__VA_ARGS__), EMPTY, CAT(FOR_EACH_I, i))(INC(i), F, __VA_ARGS__)
#define FOR_EACH_I(i, F, ...) IF(IS_EMPTY(__VA_ARGS__), EMPTY, CAT(FOR_EACH_I, i))(INC(i), F, __VA_ARGS__)
#define FOR_EACH(F, ...) FOR_EACH_I(0, F, __VA_ARGS__)

#define HELLO(x) Hello, x!

FOR_EACH(HELLO, a, b, c, d, e, f, g)

Output:

Hello, a! Hello, b! Hello, c! Hello, d! Hello, e! Hello, f! Hello, g!

何故noexceptの方がthrow()より高速なコードを生成出来るのか?

Quick Q: Why can noexcept generate faster code than throw()?—StackOverflow : Standard C++

曰く、

With the C++98 approach, the call stack is unwound to f’s caller, and, after some actions not relevant here, program execution is terminated. With the C++11 approach, runtime behavior is a bit different: the stack is only possibly unwound before program execution is terminated.

throw()だとプログラムの実行を終了する前にcall stackの巻き戻しを行うのだが、noexceptではcall stackの巻き戻しを端折っても良いからとのこと。詳細は原文を参照。本文では実際には多くの関数はnoexcept指定はされていないが自ら例外は投げない(関数内で呼び出した別の関数が投げた例外を上位レイヤーに通知することはする)例外中立なものであること等にも触れられています。

http://aristeia.com/EC++11-14/noexcept%202014-03-31.pdf

_Generic

C11から導入された機能。なんというか、色々と面白い事に使えそうな感じの機能。タプルなんかと多分相性が良い。曲芸的濫用についてはまた今度考える。
ラムダ式オーバーロードする最も簡単な方法かもしれない。 http://melpon.org/wandbox/permlink/14pwv0Akg5DhrtnA

#include <iostream>

int main() {
    auto f = [](auto arg) {
        std::cout << _Generic(arg, int: arg + 1, double: arg - 1.0, default: arg) << std::endl;
    };
    f(1);
    f(1.5);
}

複数の型の組み合わせで分岐したい場合、1つにまとめるとなんとかなる。
caseに当たる部分にはcomplete typeが要求され、tag<int, int>:と書いてもテンプレートのインスタンス化が行われずにコンパイルエラーになったが、delctype(tag<int, int>{}):とワークアラウンドを書いたところうまく動いた。
http://melpon.org/wandbox/permlink/WpLjU1hyNgcUERUX

#include <iostream>

template <typename...> struct tag {};
    
template <typename T1, typename T2>
auto f(T1 arg1, T2 arg2) {
    std::cout << _Generic(tag<T1, T2>{}, decltype(tag<int, int>{}): arg1 * arg2, decltype(tag<double, double>{}): arg1 * arg2)
              << std::endl;
}

int main() {
    f(3, 5);
    f(1.5, 2.0);
}

memo

N4072: Fixed Size Parameter Packs

O(1)

#include <type_traits>

template <typename T>
struct identity { using type = T; };

template <std::size_t N, typename Tuple>
struct at;
template <std::size_t N, template <typename...> class Tuple, typename... Types>
struct at<N, Tuple<Types...>> {
    template <typename...[N] Args, typename Arg>
    static auto impl(Args..., Arg, ...) -> Arg;
    
    using type = typename decltype(impl(identity<Types>{}...))::type;
};

再帰深度を抑えた畳込み関数

FTMPのメタ関数で使われている畳込みの実装アプローチをソースコードと勉強会で発表された資料を参考に理解した(気になった)ので、constexprで再現してみました。パックの分割に当たる操作と、相互再帰を利用して畳み込みがうまく実現されています。foldlとfoldrのコードは殆ど同じですが一応両方載せておきます。

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

#include <sprout/tuple.hpp>
#include <sprout/functional.hpp>
#include <iostream>

// foldl
template <typename Func, typename A, typename Bs>
constexpr A foldl(const Func& f, const A& a, const Bs& bs);

template <typename Func, typename A, typename B>
constexpr A foldl(const Func& f, const A& a, const sprout::tuple<B>& b);

template <typename Func, typename A>
constexpr A foldl(const Func& f, const A& a, const sprout::tuple<>&);

template <typename Func, typename A, typename Bs, sprout::index_t... LIndices,
          sprout::index_t... RIndices>
constexpr A foldl_impl(const Func& f, const A& a, const Bs& bs,
                       sprout::index_tuple<LIndices...>,
                       sprout::index_tuple<RIndices...>) {
  return foldl(
      f, foldl(f, a, sprout::forward_as_tuple(sprout::get<LIndices>(bs)...)),
      sprout::forward_as_tuple(sprout::get<RIndices>(bs)...));
}

template <typename Func, typename A, typename B>
constexpr A foldl(const Func& f, const A& a, const sprout::tuple<B>& b) {
  return f(a, sprout::get<0>(b));
}

template <typename Func, typename A>
constexpr A foldl(const Func&, const A& a, const sprout::tuple<>&) {
  return a;
}

template <typename Func, typename A, typename Bs>
constexpr A foldl(const Func& f, const A& a, const Bs& bs) {
  return foldl_impl(
      f, a, bs,
      sprout::index_range<0, sprout::tuple_size<Bs>::value / 2>::make(),
      sprout::index_range<sprout::tuple_size<Bs>::value / 2,
                          sprout::tuple_size<Bs>::value>::make());
}

// foldr
template <typename Func, typename A, typename Bs>
constexpr A foldr(const Func& f, const A& a, const Bs& bs);

template <typename Func, typename A, typename B>
constexpr A foldr(const Func& f, const A& a, const sprout::tuple<B>& b);

template <typename Func, typename A>
constexpr A foldr(const Func& f, const A& a, const sprout::tuple<>&);

template <typename Func, typename A, typename Bs, sprout::index_t... LIndices,
          sprout::index_t... RIndices>
constexpr A foldr_impl(const Func& f, const A& a, const Bs& bs,
                       sprout::index_tuple<LIndices...>,
                       sprout::index_tuple<RIndices...>) {
  return foldr(
      f, foldr(f, a, sprout::forward_as_tuple(sprout::get<RIndices>(bs)...)),
      sprout::forward_as_tuple(sprout::get<LIndices>(bs)...));
}

template <typename Func, typename A, typename B>
constexpr A foldr(const Func& f, const A& a, const sprout::tuple<B>& b) {
  return f(sprout::get<0>(b), a);
}

template <typename Func, typename A>
constexpr A foldr(const Func&, const A& a, const sprout::tuple<>&) {
  return a;
}

template <typename Func, typename A, typename Bs>
constexpr A foldr(const Func& f, const A& a, const Bs& bs) {
  return foldr_impl(
      f, a, bs,
      sprout::index_range<0, sprout::tuple_size<Bs>::value / 2>::make(),
      sprout::index_range<sprout::tuple_size<Bs>::value / 2,
                          sprout::tuple_size<Bs>::value>::make());
}


int main() {
  std::cout << foldl(sprout::plus<int>{}, 0, sprout::make_tuple(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
            << std::endl;
  std::cout << foldr(sprout::plus<int>{}, 0, sprout::make_tuple(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
            << std::endl;
}

N4072: Fixed Size Parameter Packs は期待出来る

N4072: Fixed Size Parameter Packsという提案が出ています。

参考記事:
本の虫: 2014-07 post Rapperswil mailingのレビュー: N4070-N4079

そこにこのような例があります。

template<unsigned int N, unsigned int M>
void f(int...[N], int...[M]) {}

f<2>(1,2,3); // M is deduced as 1. (N can't be deduced, but is given.)

これが可能ならば定数オーダーでパックのN番目の要素を取り出すのは以下の様に書けることが期待出来るはず。

template <unsigned int N, typename...[N] Types, typename T>
auto f(Types&&... args, T&& arg, ...) {
  return std::forward<T>(arg);
}

f<2>(1, 2, 3, 4 ,5); // 3

もはやパラメータパック分割イディオムに頼ることもなく、このようなことが出来るのですね。同様にしてtype_atのようなメタ関数も簡単に定数オーダーで実装出来るのではないでしょうか。

期待が高まります。