最終更新日時: 2025年07月10日 10時26分30秒 (JST)
Koichi Murase が更新

履歴 編集

# 関数を条件付きでexplicitにする構文を追加 [P0892R2](C++20)

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

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

## 概要

C++20では、関数を条件付きで`explicit`にする構文が追加された。コンストラクタと変換演算子に指定する`explicit`指定子に、`explicit(true)`、`explicit(false)`のように`bool`に変換可能な定数式を指定する。`true`に評価される値を指定することで、その関数は`explicit`となる。

    template <bool Cond>
    struct X {
      explicit(Cond) X() {}
    };

    X<false> f1() { return {}; }         // OK
    //X<true> f2() { return {}; }        // コンパイルエラー！
    X<false> f3() { return X<false>{}; } // OK
    X<true> f4() { return X<true>{}; }   // OK

    int main()
    {
      X<false> x1 = {};  // OK
      //X<true> x2 = {}; // コンパイルエラー！

      X<false> x3{};     // OK
      X<true> x4{};      // OK
    }

これは、関数テンプレートのコンストラクタにおいて、自身に暗黙変換可能なパラメータを受け取る場合は非`explicit`にするために使用できる。

コンストラクタだけではなく、型変換演算子もまた、条件付き`explicit`にできる：

    struct X {
      explicit(true) operator bool() const { return true; }
    };

## 例

    #include <type_traits>
    #include <string>
    #include <vector>

    template <class T>
    class AnyValue {
      T value_;
    public:
      template <class U>
      explicit(!std::is_convertible_v<U, T>)
      AnyValue(U value) : value_(value) {}

      const T& get() const { return value_; }
    };

    // const char*はstd::stringに暗黙変換可能
    AnyValue<std::string> f1() { return "Hello"; }   // OK
    AnyValue<std::string> f2() { return {"Hello"}; } // OK

    // intはstd::vector<int>に暗黙変換できないので、
    // 要素数を指定するコンストラクタを呼び出すという意図を明確に伝えないといけない
    //AnyValue<std::vector<int>> f3()
    //{
    //  return 1; // コンパイルエラー！
    //}

    //AnyValue<std::vector<int>> f4()
    //{
    //  return {1}; // コンパイルエラー！
    //}
    AnyValue<std::vector<int>> f5()
    {
      return AnyValue<std::vector<int>>{1}; // OK
    }

    int main() {}

### 出力

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

C++14では、`std::tuple`と`std::pair`の初期化子リストを使用した以下の初期化が不適格となっていた：

    std::tuple<int, int> pixel_coordinates()
    {
      return {10, -15};  // C++14でコンパイルエラー！
    }

    struct NonCopyable { NonCopyable(int); NonCopyable(const NonCopyable&) = delete; };
    std::pair<NonCopyable, double> pmd{42, 3.14};  // C++14でコンパイルエラー！

この問題に対して、C++17では (対応が早い処理系はそれ以前から) これらのクラスのコンストラクタが、条件付きで`explicit`定義されるようにした。その実装方法としてはSFINAEによって、`explicit`コンストラクタと非`explicit`コンストラクタをオーバーロードする方法が使われていた。

    template <typename T1, typename T2>
    struct pair {
      template <typename U1=T1, typename U2=T2,
        std::enable_if_t<
          std::is_constructible_v<T1, U1> &&
          std::is_constructible_v<T2, U2> &&
          std::is_convertible_v<U1, T1> &&
          std::is_convertible_v<U2, T2>
        , int> = 0>
      constexpr pair(U1&&, U2&&);

      template <typename U1=T1, typename U2=T2,
        std::enable_if_t<
          std::is_constructible_v<T1, U1> &&
          std::is_constructible_v<T2, U2> &&
          !(std::is_convertible_v<U1, T1> &&
            std::is_convertible_v<U2, T2>)
        , int> = 0>
      explicit constexpr pair(U1&&, U2&&);
    };

SFINAEによるオーバーロードはパラメータ型を制約するために依然として必要だが、関数ボディの実装が共通である`explicit`コンストラクタと非`explicit`コンストラクタは、オーバーロードではなく条件による切り替えをしたかったため、条件付きで関数を`explicit`にする構文が追加されることとなった。これを使用した場合、前述した`std::pair`のコンストラクタは、以下のように宣言できる：

    template <typename T1, typename T2>
    struct pair {
      template <typename U1=T1, typename U2=T2,
        std::enable_if_t<
          std::is_constructible_v<T1, U1> &&
          std::is_constructible_v<T2, U2>
        , int> = 0>
      explicit(!std::is_convertible_v<U1, T1> || !std::is_convertible_v<U2, T2>)
      constexpr pair(U1&&, U2&&);
    };

コンセプトを使用した場合は、以下のようになる：

    template <typename T1, typename T2>
    struct pair {
      template <typename U1=T1, typename U2=T2>
        requires std::is_constructible_v<T1, U1> &&
          std::is_constructible_v<T2, U2>
      explicit(!std::is_convertible_v<U1, T1> || !std::is_convertible_v<U2, T2>)
      constexpr pair(U1&&, U2&&);
    };

## 関連項目

- `pair`のコンストラクタ
- `tuple`のコンストラクタ
- `optional`のコンストラクタ

## 参照

- P0892R2 explicit(bool)