5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[C++14] 関数内で引数のinitializer_listのサイズに依存したメタプログラミング

Posted at

関数にstd::initializer_listを渡した時にサイズに依存してメタプログラミングしたい事がある。
典型的には、特定の要素数のみを受け取りたい時など。

まちがい

まず、私が犯した過ちは

constexpr void f (std::initializer_list<int> list)
{
    static_assert(list.size() == 3, ""); // error - listはconstexprじゃない
}

int main ()
{
    f({1, 2, 3}); // OKになってほしい
    f({1, 2});    // NGになってほしい
}

std::initializer_list<T>::size()はC++14からconstexpr指定されるが、fの定義内では、listはconstexprに見えないので、コンパイルエラーになる。
constexpr関数はconstexprでない文脈でも呼ばれる可能性があるので、constexpr関数内で引数がconstexprであることを前提にしたコードは書けない。

対策

基本的に、initializer listの構文(brace-init-list)を使って関数の実引数にstd::initializer_listを作ってしまうとサイズをconstexprで取り出すことはできない。

ところで、関数呼び出しにinitializer listの構文を使う際は、リスト初期化の方法によって引数が初期化される。
また、引数の初期化の順番は本の虫に書いてある通り。
重要な事は、引数がクラスで、initializer listが空でなく、引数の型が初期化リストコンストラクタを持たない場合、initializer listは実引数リストとみなされ、展開される。

つまり


struct foo
{
    template <typename ... Args>
    foo (Args ... args) // これが呼ばれる。
    {

    }
};

void f (foo) {}

int main ()
{
    f({1, 2, 3});
}

となる。

これを使えば、sizeof...(Args)で関数呼び出しの際の初期化リストのサイズをconstexprの文脈で取得できる。

ただし、argsfの中で使おうとすると、fooのメンバに渡すしかないが、fooのメンバの型を決めるのにinitializer listのサイズを使う事はできない。
結局、fの中でやりたいことをfooのコンストラクタに委譲するしかない。

となると最終的に次の様になる。


template <typename Func, typename ReturnType>
struct init_list_proc
{
    ReturnType value;
    template <typename ... Args>
    constexpr init_list_proc (Args && ... args) { value = Func()(std::forward<Args>(args)...); }
};

struct f_impl 
{
    template <typename ... Args>
    constexpr int operator() (Args && ... args) noexcept // ここにやりたい処理を書く
    {
        static_assert(sizeof...(Args) == 3, "");
        return 1;
    }
};

int f (init_list_proc<f_impl, int> proc)
{
    return proc.value;
}

int main ()
{
    f({1, 2, 3}); //OK
    f({1, 2}); // NG
}

条件でfの型を変えたい場合はf_impl::operator()をオーバーロードして、fの方もオーバーロードすれば良い。
ただし、これでもまだ、fの返り値の型をメタプログラミングで導出する事はできない。

もっと簡単な方法がある気もする。

参考

本の虫
cppreference

5
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?