• Class / Function / Type

      std::
    • Header file

      <>
    • Other / All

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

    履歴 編集

    using宣言のパック展開 [P0195R2]

    このページはC++17に採用された言語機能の変更を解説しています。

    のちのC++規格でさらに変更される場合があるため関連項目を参照してください。

    概要

    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でパック展開ができるようになった。

    関連項目

    参照