【C++】テンプレートパラメータに制約を設ける


例えば、掛け算の結果が型の範囲内であればその値を返す関数があるとします。
ここで、T=浮動小数点型としての使用は想定していない (整数型のみ指定可能にしたい) ケースについて考えてみます。

template<typename T>
std::optional<T> times(T value1, T value2) {
    long long result = value1 * value2;
    return std::numeric_limits<T>::lowest() <= result
        && result <= std::numeric_limits<T>::max()
            ? std::make_optional<T>(static_cast<T>(result))
            : std::nullopt;
}

目次

static_assert

まずお手軽な方法としては static_assert を使う方法があります

template<typename T>
std::optional<T> times(T value1, T value2) {
    static_assert(std::is_integral_v<T>, "times supports integral value only.");
    // 処理...
}

std::is_integral_v<T> は、Tが整数型かどうかのbool値を返します。
従って、整数型以外の値を渡そうとした場合にはコンパイルエラーになります。

ただ static_assert を使用する方法だと、同じように浮動小数点型のテンプレート関数を定義したい場合に、オーバーロードすることができません。
(型Tの判定処理が実装内にあるため、オーバーロード解決ができない)

これから紹介する std::enable_ifconcept はその点をカバーしています。

std::enable_if

std::enable_ifSFINAE と組み合わせる方法です。

色々書き方はありますが、戻り値や引数に使用するとこんな感じで書けます

// 戻り値Ver
template<typename T>
std::enable_if_t<std::is_integral_v<T>, std::optional<T>> times(T value1, T value2) {
    // 処理...
}

// 引数Ver
template<typename T>
std::optional<T> times(std::enable_if_t<std::is_integral_v<T>, T> value1, T value2) {
    // 処理...
}

順に説明すると、

  • std::enable_if_t<Condition, T> はConditionがtrueの場合に型Tに置き換わります
  • Conditionがfalseになる場合は型Tに変換されないため、そんな関数「times」は無いとして、オーバーロードの候補から外れます

上記は戻り値や引数が汚れてしまう (関数のIFの情報と、テンプレートパラメータについての情報がごっちゃになっている) ため、
それが嫌な場合はテンプレートパラメータを増やす方法もあります

template<typename T, std::enable_if_t<std::is_integral_v<T>>* U = nullptr>
std::optional<T> times(T value1, T value2) {
    // 処理...
}
  • std::enable_if_t<Condition, T> のTのデフォルト引数はvoidのため、Conditionがtrueの場合、関数「times」のテンプレートパラメータUの型は「void*」になります
  • Conditionがfalseの場合はオーバーロードの候補から外れます

concepts

C++20 になると concepts が登場します。
std::integral を使えば一瞬で解決です。

template<std::integral T>
std::optional<T> times(T value1, T value2) {
    // 処理...
}

以上です!