最終更新日時(UTC):
が更新

履歴 編集

using宣言のパック展開(C++17)

概要

C++17にてusing宣言の仕様が拡張され、パラメータパックが指定できるようになった。 具体的にはusing宣言の識別子をカンマで区切ること、 パラメータパックに省略記号...を指定してパック展開が可能になる。

C++14では下記のように1つずつ宣言していたが、

using std::cout;
using std::endl;

下記のように一度に宣言できるようになった。 1つ目の識別子と名前空間が同じであっても省略はできないため、 2番目のような書き方はできない。

using std::cout, std::endl;

//using std::cout, endl; // NG

省略記号...を指定しパック展開を行う例は下記の通り。

template <typename... T>
struct A : T... {
  using T::operator()...; // 受け取ったクラスのoperator()を全て使えるようにする
};

仕様

文法の仕様は下記の通り。

  using-declaration:
    using using-declarator-list ;

  using-declarator-list:
    using-declarator ...opt
    using-declarator-list , using-declarator ...opt

ForAlloperator()(int)メンバ関数を持つクラステンプレートである。 複数のクラスをテンプレートパラメータに受け取り、受け取った全てのクラスを継承する。 テンプレートパラメータのパック展開を行うusing宣言により、 継承した全てのクラスのoperator()を使えるようにしている。

この例ではlongstd::stringを引数として渡すとForAll::operator()(int)ではなく、 using宣言したForLong::operator()(long)ForString::operator()(cons std::string&)が呼び出される。

#include <iostream>

struct ForLong {
  void operator()(long v) {
    std::cout << "ForLong:" << v << std::endl;
  }
};

struct ForString {
  void operator()(const std::string& v) {
    std::cout << "ForString:" << v << std::endl;
  }
};

template <typename... T>
struct ForAll : T... {
  using T::operator()...;
  void operator()(int v) {
    std::cout << "ForAll:" << v << std::endl;
  }
};

int main()
{
  ForAll<ForLong, ForString> p;
  p(10);
  p(100L);
  p("hello");
}

出力

ForAll:10
ForLong:100
ForString:hello

この機能が必要になった背景・経緯

C++11にて可変引数テンプレートが導入され、 テンプレートパラメータに渡されたクラスをパック展開し、一度に全て継承することができるようになった。

template <typename... T>
struct ForAll2 : T... {
  // Tに指定されたクラスを全て継承
};

しかしC++11やC++14ではパラメータパックに指定されたクラスが持つメンバ関数を、 全てusing宣言する簡単な方法がなかった。 このためクラステンプレートが基底クラスと派生クラスでメンバ関数をオーバーロードする場合、 実装が煩雑になってしまう問題があった。

#include <iostream>

struct ForLong {
  void operator()(long v) {
    std::cout << "ForLong:" << v << std::endl;
  }
};

struct ForString {
  void operator()(const std::string& v) {
    std::cout << "ForString:" << v << std::endl;
  }
};

// C++14までの方法、C++17でも使えるが冗長

// クラスを2つ以上受け取る場合、先頭のクラス T とそれ以外 Ts に分割する
template <typename T, typename... Ts>
struct ForAll2 : T, ForAll2<Ts...> {
  using T::operator(); // 先頭のクラス T の operator() を使えるようにする
  using ForAll2<Ts...>::operator(); // 残りのクラスを再帰的に使えるようにする
};

// クラスを1つだけ受け取る場合、特殊な処理を行う(部分特殊化)
//
// クラステンプレートのみだと、クラスを1つしか受け取らない場合に
// Ts... が空になって ForAll2<Ts...> が文法エラーになる
// このためクラスが1つの場合は特別扱いする必要がある
template <typename T>
struct ForAll2<T> : T {
  using T::operator();
  void operator()(int v) {
    std::cout << "ForAll2:" << v << std::endl;
  }
};

int main()
{
  ForAll2<ForLong, ForString> p;
  p(20);
  p(200L);
  p("hello2");
}

出力

ForAll2:20
ForLong:200
ForString:hello2

この問題を解決するためC++17ではusingでパック展開ができるようになった。

関連項目

参照