Edited at

static_assertの評価を実体化まで遅延させる簡単な方法


はじめに

static_assert1の評価をインスタンス化2まで遅延させるためにはfalse_v<T>3を使うのが一般的(?)ですが、条件式に[]{return false;}()を使うと、それよりも少しだけ簡単に書けます。なぜそのようなことをするかは、ここなどを参照してみてください。


コード例

#include <type_traits>


template<typename T>
T get() {
if constexpr (std::is_same_v<int, T>) return 0;
else static_assert([]{return false;}(), "...");
}

int main() {
get<int>(); // static_assertは評価されません
}


解説

(この解説は、読まなくても大丈夫です!!)

上のコードは、次のコードとだいたい同じような意味です。

#include <type_traits>


template<typename T>
T get() {
struct lambda {
constexpr auto operator()() const -> decltype(false) {
return false;
}
};
if constexpr (std::is_same_v<int, T>) return 0;
else static_assert(lambda{}(), "...");
}

int main() {
get<int>();
}


ラムダ式の型(これはクロージャオブジェクトの型でもあります)は、クロージャ型と呼ばれる固有の名前のない共用体ではないクラス型です。そのプロパティについては後述します。

クロージャ型は、対応するラムダ式を含む最小のブロックスコープ、クラススコープ、またはネームスペーススコープで宣言されます。(google翻訳)

http://eel.is/c++draft/expr.prim.lambda.closure


より、この場合のラムダ式4get関数テンプレート5内のブロックスコープ6に含まれているので、クロージャ型はそこで宣言されます。ここでは、仮にlocal struct lambda7としておきます(定義までしてしまっています)。


[Note: Within a template declaration, a local class (12.4) or enumeration and the members of a local class are never considered to be entities that can be separately instantiated (this includes their default arguments, noexcept-specifiers, and non-static data member initializers, if any). As a result, the dependent names are looked up, the semantic constraints are checked, and any templates used are instantiated as part of the instantiation of the entity within which the local class or enumeration is declared. -- end note]

C++17言語仕様 §17.7.1 Implicit instantiation [temp.inst], Paragraph 1

(google翻訳するとかえって読みにくくなったので、原文で載せています)


より、テンプレート関数内のlocal class(struct)は依存名8とみなすので、local struct lambdaget()のテンプレートパラメータ9Tに依存します。よって、two-phase name lookup10 によりlambda{}()11の評価は、get()がインスタンス化されるまで遅延されます。したがって、static_assertの評価も同様にget()がインスタンス化されるまで遅延されます。(結果として、constexpr ifstatic_assertのブランチは破棄されています)


おわりに

teratailの投稿にて回答してくださった yumetodoさん、Chironianさん、yohhoyさんに感謝します!