最終更新日時:
が更新

履歴 編集

SFINAEが適用される「直接文脈」を定義 [P4149R1](C++26)

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

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

概要

C++26では、テンプレートにおける「SFINAE(置換失敗はエラーではない)」が適用される範囲を表す用語「直接文脈 (immediate context)」が正式に定義される。

この用語は、C++11で任意の式によるSFINAEが導入されて以来、定義がないまま規格中で使われ続けており、とくに「デフォルト引数の中での置換失敗はSFINAEになるのか、それともコンパイルエラーなのか」の扱いが処理系ごとに異なっていた。C++26では次のように定義されることになる。

  • テンプレート化された関数のデフォルト引数・noexcept指定・関数契約指定子を、まとめて「個別にインスタンス化される構成要素 (separately instantiated construct)」と呼ぶ
  • テンプレート引数の置き換えは、関数のシグネチャ(戻り値型・引数型)やテンプレートパラメータの宣言などに対して行われる。直接文脈は、この置き換えが行われる箇所から、ラムダ式の本体と個別にインスタンス化される構成要素を除いたものである

置き換えの結果が無効な型や式になったとき、オーバーロード候補からの除外(SFINAE)となるのは直接文脈の中だけであり、直接文脈の外で無効な型・式が現れた場合はコンパイルエラーとなる。この定義によって、次の動作が明確になる。

// 関数のシグネチャ(戻り値型・引数型)は直接文脈:
// T::typeが無効でも、この候補が除外されるだけでエラーにはならない
template <class T>
typename T::type f(T x);

// デフォルト引数は直接文脈の外:
// このオーバーロードが選択されてデフォルト引数が必要になったとき、
// T::valueが無効ならSFINAEにならずコンパイルエラーとなる
template <class T>
void g(T x = T::value);

noexcept指定と関数契約指定子は、従来から関数の宣言とは個別に「必要になったときのみ」インスタンス化されると規定されており、今回それらとデフォルト引数の扱いが「個別にインスタンス化される構成要素」として統一された。

仕様

  • 「個別にインスタンス化される構成要素」という用語が定義される。テンプレート化された関数のデフォルト引数、noexcept指定、関数契約指定子がこれにあたる
    • 名前探索とインスタンス化の目的では、個別にインスタンス化される構成要素は、関数本体や互いとは無関係な、それぞれ独立した定義として扱われる
  • 「直接文脈」という用語が定義される。テンプレート引数の置き換えが行われる各箇所(関数型・テンプレートパラメータ宣言・explicit指定の条件式。規格ではまとめてdeduction substitution lociと呼ばれる)の直接文脈は、その箇所から、ラムダ式の本体と個別にインスタンス化される構成要素を除いたものである
    • 置き換えの結果が無効な型・式になったことがテンプレート引数推論の失敗(SFINAE)となるのは、直接文脈の中だけである
    • 個別にインスタンス化される構成要素は、たとえ関数の宣言と同時にインスタンス化される場合でも直接文脈から除外される
    • いかなる文 (statement) も直接文脈には含まれない
  • ローカルクラスのメンバ関数、および非ジェネリックラムダのクロージャ型の関数呼び出し演算子については、個別にインスタンス化される構成要素は、その関数の宣言のインスタンス化と同時にインスタンス化される(ただし依然として関数とは個別の定義として扱われ、直接文脈にも含まれない)
  • 例外仕様が「必要 (needed)」となる条件の一覧に、「関数がリフレクションで表現されるとき」が追加される

#include <iostream>

// 関数型(戻り値型・引数型)は直接文脈:置換失敗はSFINAEとなる
template <class T>
T* fun(T&& v) { std::cout << "#1" << std::endl; return nullptr; }

void fun(...) { std::cout << "#2" << std::endl; }

int main() {
  int i = 0;
  fun(i);  // T = int& での置換は「参照へのポインタ」の形成に失敗するが、
           // 関数型は直接文脈なのでエラーにならず、#2が選択される
}

出力

#2

一方、デフォルト引数の中での置換失敗はSFINAEにならない。

template <class T>
void g(T x = T::not_found) {}

template <class T>
concept canG = requires { g<T>(0); };

// デフォルト引数は直接文脈の外なので、T::not_foundの置換失敗は
// canG<int>をfalseにせず、コンパイルエラーとなる
constexpr bool b = canG<int>;  // コンパイルエラー!

なお、2026年9月時点の処理系の動作は分かれており(このケースをGCC・MSVCはSFINAEとして扱い、Clangはコンパイルエラーとする)、統一された規則を完全に実装した処理系はまだない。

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

「immediate context」という語は、C++11の任意の式によるSFINAEの導入時から規格中で使われていたが、その正確な範囲は定義されていなかった(CWG 2296)。実際に、デフォルト引数の中での置換失敗の扱いは処理系間で一致しておらず、C++26の国際投票でこの用語を定義するよう要求するNBコメント(US 54-100)が提出された。

検討の過程では、デフォルト引数を直接文脈に「含める」案と「含めない」案の両方が投票にかけられ、含めない方向で合意された。その上で、デフォルト引数・noexcept指定・関数契約指定子(およびアノテーション)の扱いを「個別にインスタンス化される構成要素は直接文脈に含めず、それ以外(ラムダ式の本体を除く)は含める」という一貫した規則へ統一した。アノテーションについては、noexcept指定と同じ「必要になったときのみインスタンス化される」規則に従うこととし、直接文脈には含まれない。

関連項目

参照