C++17 完全ガイド - テンプレート機能を備えたコンパイラの if 文

構文 を使用するとif constexpr(...)コンパイラはコンパイル時の条件式を評価してif、コンパイル時にステートメントのthen部分とelse部分のどちらを使用するかを決定できます。コードの残りの部分は破棄されますつまり、コードは生成されませんただし、これは破棄された部分が完全に無視されるという意味ではなく、これらの部分のコードも未使用のテンプレートなどの構文がチェックされます。

例えば:

#include <string>

template <typename T>
std::string asString(T x)
{
    
    
    if constexpr(std::is_same_v<T, std::string>) {
    
    
        return x;                   // 如果T不能自动转换为string该语句将无效
    }
    else if constexpr(std::is_arithmetic_v<T>) {
    
    
        return std::to_string(x);   // 如果T不是数字类型该语句将无效
    }
    else {
    
    
        return std::string(x);      // 如果不能转换为string该语句将无效
    }
}

を使用すると、渡された文字列を単に返すか、渡された数値を呼び出すか、コンストラクターを使用して渡されたパラメータを に変換するかif constexprをコンパイル時に決定できます無効な呼び出しは破棄されるため、次のコードはコンパイルされます (ランタイム ステートメントを使用する場合はコンパイルされません)。to_string()std::stringif

#include <iostream>

int main()
{
    
    
    std::cout << asString(42) << '\n';
    std::cout << asString(std::string("hello")) << '\n';
    std::cout << asString("hello") << '\n';
}

プリコンパイルされたコードは次のとおりです。

//代码std::cout << asString(42) << '\n';的预编译代码如下:
/* First instantiated from: insights.cpp:19 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char, std::char_traits<char>, std::allocator<char> > asString<int>(int x)
{
    
    
  if constexpr(false) {
    
    
  } else /* constexpr */ {
    
    
    if constexpr(true) {
    
    
      return std::to_string(x);
    } 
    
  } 
  
}
#endif

//代码std::cout << asString(std::string("hello")) << '\n';的预编译代码如下:
/* First instantiated from: insights.cpp:20 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char, std::char_traits<char>, std::allocator<char> > asString<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >(std::basic_string<char, std::char_traits<char>, std::allocator<char> > x)
{
    
    
  if constexpr(true) {
    
    
    return std::basic_string<char, std::char_traits<char>, std::allocator<char> >(static_cast<std::basic_string<char, std::char_traits<char>, std::allocator<char> > &&>(x));
  } 
}
#endif

//代码std::cout << asString("hello") << '\n';的预编译代码如下:
/* First instantiated from: insights.cpp:21 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char, std::char_traits<char>, std::allocator<char> > asString<const char *>(const char * x)
{
    
    
  if constexpr(false) {
    
    
  } else /* constexpr */ {
    
    
    if constexpr(false) {
    
    
    } else /* constexpr */ {
    
    
      return std::basic_string<char, std::char_traits<char>, std::allocator<char> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >(x, std::allocator<char>()));
    } 
  } 
}
#endif


int main()
{
    
    
  std::operator<<(std::operator<<(std::cout, asString(42)), '\n');
  std::operator<<(std::operator<<(std::cout, asString(std::basic_string<char, std::char_traits<char>, 	   std::allocator<char> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >("hello", std::allocator<char>())))), '\n');
  std::operator<<(std::operator<<(std::cout, asString("hello")), '\n');
  return 0;
}

コンパイル時ifステートメント

上記の例でランタイムを使用したif場合、次のコードはコンパイルできません。

#include <string>

template <typename T>
std::string asString(T x)
{
    
    
    if (std::is_same_v<T, std::string>) {
    
    
        return x;                   // 如果不能自动转换为string会导致ERROR
    }
    else if (std::is_numeric_v<T>) {
    
    
        return std::to_string(x);   // 如果不是数字将导致ERROR
    }
    else {
    
    
        return std::string(x);      // 如果不能转换为string将导致ERROR
    }
}

これは、テンプレートがインスタンス化されるときに全体としてコンパイルされるためです。ただし、ifステートメントの条件式のチェックは実行時機能です。コンパイル時に条件式の値が でなければならないと判断できる場合でもfalsethen部分もコンパイルに合格できなければなりません。したがって、std::stringまたは 文字列リテラルを渡すと、std::to_string()無効であるためコンパイルに失敗します。また、数値を渡す場合、最初と 3 番目の return ステートメントが無効であるため、コンパイルは失敗します。

コンパイル時ステートメントを使用する場合if使用できないthenおよびelse部分は破棄されたステートメントになります。

  • one を渡すと、std::string最初のifステートメントのelse部分は破棄されます。
template<>
std::basic_string<char, std::char_traits<char>, std::allocator<char> > asString<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >(std::basic_string<char, std::char_traits<char>, std::allocator<char> > x)
{
    
    
  if constexpr(true) {
    
    
    return std::basic_string<char, std::char_traits<char>, std::allocator<char> >(static_cast<std::basic_string<char, std::char_traits<char>, std::allocator<char> > &&>(x));
  } 
  
}
  • 数値を渡す場合、if最初のステートメントのthen部分と最後のelse部分は破棄されます。
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char, std::char_traits<char>, std::allocator<char> > asString<int>(int x)
{
    
    
  if constexpr(false) {
    
    
  } else /* constexpr */ {
    
    
    if constexpr(true) {
    
    
      return std::to_string(x);
    } 
  } 
}
#endif
  • 文字列リテラル (型 ) を渡す場合、const char*最初と 2 番目のifステートメントのthen部分は破棄されます。
#ifdef INSIGHTS_USE_TEMPLATE
template<>
std::basic_string<char, std::char_traits<char>, std::allocator<char> > asString<const char *>(const char * x)
{
    
    
  if constexpr(false) {
    
    
  } else /* constexpr */ {
    
    
    if constexpr(false) {
    
    
    } else /* constexpr */ {
    
    
      return std::basic_string<char, std::char_traits<char>, std::allocator<char> >(std::basic_string<char, std::char_traits<char>, std::allocator<char> >(x, std::allocator<char>()));
    } 
  } 
}
#endif

したがって、インスタンス化のたびに無効なブランチがコンパイル時に破棄されるため、コードは正常にコンパイルされます。削除されたステートメントは無視されないことに注意してください。無視されるステートメントであっても正しい構文に準拠する必要があり、テンプレート パラメーターに関連しないすべての呼び出しも正しい必要があります。実際、テンプレートのコンパイルの最初のフェーズ (定義中) では、すべてのテンプレートに依存しない名前の構文と有効性がチェックされます。static_assertsブランチがコンパイルされていない場合でも、すべてが有効である必要があります。

例えば:

template<typename T>
void foo(T t)
{
    
    
    if constexpr(std::is_integral_v<T>) {
    
    
        if (t > 0) {
    
    
            foo(t-1);   // OK
        }
    }
    else {
    
    
        undeclared(t); // 如果未被声明且未被丢弃将导致错误
        undeclared();  // 如果未声明将导致错误(即使被丢弃也一样)
        static_assert(false, "no integral"); // 总是会进行断言(即使被丢弃也一样)
    }
}

標準に準拠したコンパイラの場合、上記の例はコンパイルでき、次のようなエラーが発生します。

<source>:17:9: error: there are no arguments to 'undeclared' that depend on a template parameter, so a declaration of 'undeclared' must be available [-fpermissive]
   17 |         undeclared();  // 如果未声明将导致错误(即使被丢弃也一样)
      |         ^~~~~~~~~~
<source>:17:9: note: (if you use '-fpermissive', G++ will accept your code, but allowing the use of an undeclared name is deprecated)
<source>:18:23: error: static assertion failed: no integral
   18 |         static_assert(false, "no integral"); // 总是会进行断言(即使被丢弃也一样)

理由は 2 つあります

  • T整数型でも、次のように呼び出されます。
undeclared(); // 如果未声明将导致错误(即使被丢弃也一样)

関数が未定義の場合、破棄されたelse部分にある場合でも、呼び出しはテンプレートパラメータに依存しないため、エラーが発生します

  • 次のようにアサートします
static_assert(false, "no integral"); // 总是会进行断言(即使被丢弃也一样)

破棄されたelse部分でさえ、テンプレート パラメーターにも依存しないため、アサーションは常に失敗しますコンパイル時の条件を使用する静的アサーションには、この問題はありません。

static_assert(!std::is_integral_v<T>, "no integral");

一部のコンパイラ (例:Visual C++ 2013および2015) は、テンプレート コンパイルの 2 つのフェーズを正しく実装していないことに注意してください。ほとんどの作業が第 1 フェーズ (定義中) から第 2 フェーズ (インスタンス化中)に延期されるため、一部の無効な関数呼び出しや一部の不正な構文がコンパイルされる可能性があります

コンパイル時ifステートメントを使用する

理論的に言えば、条件式がコンパイル時式である限り、ifコンパイル時を runtime として使用できますifコンパイル時と実行時を混在させることもできますif

if constexpr (std::is_integral_v<std::remove_reference_t<T>>) {
    
    
    if (val > 10) {
    
    
        if constexpr (std::numeric_limits<char>::is_signed) {
    
    
            ...
        }
        else {
    
    
            ...
        }
    }
    else {
    
    
        ...
    }
}
else {
    
    
    ...
}

関数本体の外では使用できないことに注意してくださいif constexprしたがって、これをプリプロセッサの条件付きコンパイルの代わりに使用することはできません。

コンパイル時のif注意事項

コンパイル時ifに使用すると、明らかではない結果が生じる可能性があります

コンパイルはif戻り値の型に影響します

関数の戻り値の型はコンパイル時に影響を受けるif可能性がありますたとえば、次のコードは常にコンパイルされますが、戻り値の型は異なる場合があります。

auto foo()
{
    
    
    if constexpr (sizeof(int) > 4) {
    
    
        return 42;
    }
    else {
    
    
        return 42u;
    }
}

ここでは を使用しているためauto、戻り値の型は return ステートメントによって異なり、どの return ステートメントが実行されるかはintバイト数によって異なります。

  • 4バイトより大きい場合は返された42return文が有効となるため、戻り値の型は となりますint
  • それ以外の場合は、返された42ureturn ステートメントが有効になるため、戻り値の型は になりますunsigned int
    前処理コードは次のとおりです。
unsigned int foo()
{
    
    
  if constexpr(false) {
    
    
    return 42;
  } else /* constexpr */ {
    
    
    return 42U;
  } 
  
}

この場合、if constexprステートメントを含む関数はまったく異なる型を返す可能性があります。たとえば、 else部分を記述しない場合、戻り値はint次のいずれかになりますvoid

auto foo()  // 返回值类型可能是int或者void
{
    
    
    if constexpr (sizeof(int) > 4) {
    
    
        return 42;
    }
}

前処理コードは次のとおりです。

void foo()
{
    
    
  if constexpr(false) {
    
    
    return 42;
  } 
  
}
//或者
int foo()
{
    
    
  if constexpr(true) {
    
    
    return 42;
  } 
  
}

ここでランタイムが使用される場合if、コードは決してコンパイルされないことに注意してください。戻り値の型を導出するときに、考えられるすべての戻り値の型が考慮されるため、導出は曖昧になります
例えば

#include <iostream>
#include <string>
#include <utility>

using namespace std;

auto foo() {
    
    
    if (sizeof(int) > 4) {
    
    
        return 42;
    } else {
    
    
        return 42u;
    }
}

int main() {
    
     return 0; }

次のエラーが発生します。

<source>:13:16: error: inconsistent deduction for auto return type: 'int' and then 'unsigned int'
   13 |         return 42u;
      |                ^~~

then部分でreturn してもelse部分を考慮します

ifコンパイル時には適用されないパターンが実行時にありますif。コードがthen 部分else部分の両方で返る場合、if実行時elseその部分をスキップできます。つまり、

if (...) {
    
    
    return a;
}
else {
    
    
    return b;
}

次のように書くことができます:

if (...) {
    
    
    return a;
}
return b;

ただし、このパターンはコンパイル時に適用できませんif。2 番目の方法では、戻り値の型がどちらかの return ステートメントではなく両方の return ステートメントに依存し、動作の変更につながるためです。たとえば、上記の例に従ってコードを変更した場合、コンパイルできる場合とできない場合があります

auto foo()
{
    
    
    if constexpr (sizeof(int) > 4) {
    
    
        return 42;
    }
    return 42u;
}

条件式が true ( int4 バイトを超える) の場合、コンパイラは2 つの異なる戻り値の型を推定し、エラーが発生しますそれ以外の場合、有効な return ステートメントは 1 つだけになるため、コードはコンパイルされます。
考えられるエラーは次のとおりです。

<source>:11:12: error: inconsistent deduction for auto return type: 'int' and then 'unsigned int'
   11 |     return 42u;
      |            ^~~

コンパイル時のショート評価

次のコードを考えてみましょう。

template<typename T>
constexpr auto foo(const T& val)
{
    
    
    if constexpr(std::is_integral<T>::value) {
    
    
        if constexpr (T{
    
    } < 10) {
    
    
            return val * 2;
        }
    }
    return val;
}

ここでは、2 つのコンパイル時条件を使用して、渡された値を直接返すか、渡された値の 2 倍を返すかを決定します

次のコードはすべてコンパイルされます。

constexpr auto x1 = foo(42);    // 返回84
constexpr auto x2 = foo("hi");  // OK,返回"hi"

前処理コードは次のとおりです。

#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline constexpr int foo<int>(const int & val)
{
    
    
  if constexpr(true) {
    
    
    if constexpr(true) {
    
    
      return val * 2;
    } 
    
  } 
  
  return val;
}
#endif


#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline constexpr const char * foo<char[3]>(const char (&val)[3])
{
    
    
  if constexpr(false) {
    
    
  } 
  
  return val;
}
#endif


int main()
{
    
    
  constexpr const int x1 = foo(42);
  constexpr const char *const x2 = foo("hi");
  return 0;
}

実行時の条件式はif、短絡評価を行います (&&左辺がfalseの場合は評価を停止し、||左辺がtrueの場合は評価を停止します)。これにより、ifコンパイル時に期待される評価が短絡される可能性もあります。

template<typename T>
constexpr auto bar(const T& val)
{
    
    
    if constexpr (std::is_integral<T>::value && T{
    
    } < 10) {
    
    
        return val * 2;
    }
    return val;
}

ただし、コンパイル時のif条件式は常に全体としてインスタンス化され、全体として有効である必要があります。つまり、<10評価できない型を渡すとコンパイルに失敗します。

constexpr auto x2 = bar("hi");  // 编译期ERROR

エラーメッセージは次のとおりです。

<source>: In instantiation of 'constexpr auto bar(const T&) [with T = char [3]]':
<source>:17:28:   required from here
<source>:10:53: error: taking address of temporary array
   10 |     if constexpr (std::is_integral<T>::value && T{
    
    } < 10) {
    
    
      |                                                 ~~~~^~~~
<source>:11:20: error: invalid operands of types 'const char [3]' and 'int' to binary 'operator*'
   11 |         return val * 2;
      |                ~~~~^~~
<source>: In function 'int main()':
<source>:17:28: error: 'constexpr auto bar(const T&) [with T = char [3]]' called in a constant expression
   17 |     constexpr auto x2 = bar("hi");
      |                         ~~~^~~~~~
<source>:8:16: note: 'constexpr auto bar(const T&) [with T = char [3]]' is not usable as a 'constexpr' function because:
    8 | constexpr auto bar(const T& val)
      |         

したがって、コンパイル時はインスタンス化時if評価を短縮しません後者の条件の有効性が前の条件に依存する場合は、条件をネストする必要がありますたとえば、次のように記述する必要があります。

if constexpr (std::is_same_v<MyType, T>) {
    
    
    if constepxr (T::i == 42) {
    
    
        ...
    }
}

書く代わりに:

if constexpr (std::is_same_v<MyType, T> && T::i == 42) {
    
    
    ...
}

その他のコンパイル時間ifの例

完全な戻り値のジェネリック値

if1 つのアプリケーションでは、コンパイル時に最初に戻り値に対して何らかの処理を実行し、次に完全転送を実行します(不完全な型であるため)decltype(auto)推定できないため、次のように記述する必要があります。voidvoid

#include <functional>  // for std::forward()
#include <functional>  // for std::forward()
#include <iostream>
#include <type_traits>  // for std::is_same<> and std::invoke_result<>

using namespace std;

double foo1(int x, int y) {
    
     return x * 2.5 + y; }
void foo2() {
    
     cout << "foo2" << endl; }

template <typename Callable, typename... Args>
decltype(auto) call(Callable op, Args&&... args) {
    
    
    if constexpr (std::is_void_v<std::invoke_result_t<Callable, Args...>>) {
    
    
        // 返回值类型是void:
        op(std::forward<Args>(args)...);
        return;
    } else {
    
    
        // 返回值类型不是void:
        decltype(auto) ret{
    
    op(std::forward<Args>(args)...)};
        return ret;
    }
}
int main() {
    
    
    double tv1 = call(foo1, 5, 3);
    cout << tv1 << endl;
    call(foo2);
    return 0;
}

関数の戻り値の型は として推定できますvoidが、retの宣言は として推定できないvoidため、op戻りvoid値の場合は個別に処理する必要があります。
操作の結果は次のようになります。

15.5
foo2

プリコンパイルされたコードは次のとおりです。

#include <functional>  // for std::forward()
#include <iostream>
#include <type_traits>  // for std::is_same<> and std::invoke_result<>

using namespace std;

double foo1(int x, int y)
{
    
    
  return (static_cast<double>(x) * 2.5) + static_cast<double>(y);
}

void foo2()
{
    
    
  std::operator<<(std::cout, "foo2").operator<<(std::endl);
}


template<typename Callable, typename ... Args>
decltype(auto) call(Callable op, Args &&... args)
{
    
    
  if constexpr(std::is_void_v<std::invoke_result_t<Callable, Args...> >) {
    
    
    op(std::forward<Args>(args)... );
    return;
  } else /* constexpr */ {
    
    
    decltype(auto) ret = {
    
    op(std::forward<Args>(args)... )};
    return ret;
  } 
  
}


#ifdef INSIGHTS_USE_TEMPLATE
template<>
double call<double (*)(int, int), int, int>(double (*op)(int, int), int && __args1, int && __args2)
{
    
    
  if constexpr(false) {
    
    
  } else /* constexpr */ {
    
    
    double ret = {
    
    op(std::forward<int>(__args1), std::forward<int>(__args2))};
    return ret;
  } 
  
}
#endif


#ifdef INSIGHTS_USE_TEMPLATE
template<>
void call<void (*)()>(void (*op)())
{
    
    
  if constexpr(true) {
    
    
    op();
    return;
  } 
  
}
#endif

int main()
{
    
    
  double tv1 = call(foo1, 5, 3);
  std::cout.operator<<(tv1).operator<<(std::endl);
  call(foo2);
  return 0;
}

コンパイル時にif型をディスパッチする

コンパイル時ifの一般的な使用法は、型dispatchです。が登場する前はC++17、処理したい型ごとに個別の関数をオーバーライドする必要がありました。今では、コンパイル時にifすべてのロジックを関数に入れることができます。

たとえば、次のstd::advance()アルゴリズムのオーバーロードされたバージョンは次のとおりです。

template<typename Iterator, typename Distance>
void advance(Iterator& pos, Distance n) {
    
    
    using cat = std::iterator_traits<Iterator>::iterator_category;
    advanceImpl(pos, n, cat{
    
    }); // 根据迭代器类型进行分发
}

template<typename Iterator, typename Distance>
void advanceImpl(Iterator& pos, Distance n, std::random_access_iterator_tag) {
    
    
    pos += n;
}

template<typename Iterator, typename Distance>
void advanceImpl(Iterator& pos, Distance n, std::bidirectional_iterator_tag) {
    
    
    if (n >= 0) {
    
    
        while (n--) {
    
    
            ++pos;
        }
    }
    else {
    
    
        while (n++) {
    
    
            --pos;
        }
    }
}

template<typename Iterator, typename Distance>
void advanceImpl(Iterator& pos, Distance n, std::input_iterator_tag) {
    
    
    while (n--) {
    
    
        ++pos;
    }
}

すべての実装を同じ関数に入れることができるようになりました。

template<typename Iterator, typename Distance>
void advance(Iterator& pos, Distance n) {
    
    
    using cat = std::iterator_traits<Iterator>::iterator_category;
    if constexpr (std::is_convertible_v<cat, std::random_access_iterator_tag>) {
    
    
        pos += n;
    }
    else if constexpr (std::is_convertible_v<cat, std::bidirectional_access_iterator_tag>) {
    
    
        if (n >= 0) {
    
    
            while (n--) {
    
    
                ++pos;
            }
        }
        else {
    
    
            while (n++) {
    
    
                --pos;
            }
        }
    }
    else {
    
      // input_iterator_tag
        while (n--) {
    
    
            ++pos;
        }
    }
}

ここではコンパイル期間があるようでswitch、各if constexprステートメントは 1 つのもののようですcaseただし、この例の 2 つの実装には 1 つの違いがあることに注意してください。

  • オーバーロードされた関数のバージョンは、最も一致するセマンティクスに従います。
  • コンパイル時のバージョンは、最初に一致するifセマンティクスに従います

型ディスパッチのもう 1 つの例は、コンパイル時のif実装get<>()オーバーロードを使用して構造化バインディング インターフェイスを実装することです。
3 番目の例は、std::variant<>アクセサーとして使用されるジェネリックlambdaのさまざまな型を扱うことです。

初期化を伴うコンパイル時ifステートメント

コンパイル時のifステートメントでは、新しい初期化された形式も使用できることに注意してください。たとえば、constexprfunction がある場合はfoo()、次のように記述できます。

#include <functional>  // for std::forward()
#include <iostream>
#include <type_traits>  // for std::is_same<> and std::invoke_result<>

using namespace std;

double foo(int x) {
    
     return x * 2.5; }

template <typename T>
void bar(const T x) {
    
    
    if constexpr (auto obj = foo(x); std::is_same_v<decltype(obj), T>) {
    
    
        std::cout << "foo(x) yields same type\n";
    } else {
    
    
        std::cout << "foo(x) yields different type\n";
    }
}
int main() {
    
    
    bar(2);
    bar(2.5);
    return 0;
}

操作の結果は次のようになります。

foo(x) yields different type
foo(x) yields same type

前処理コードは次のとおりです。

#include <functional>  // for std::forward()
#include <iostream>
#include <type_traits>  // for std::is_same<> and std::invoke_result<>

using namespace std;

double foo(int x)
{
    
    
  return static_cast<double>(x) * 2.5;
}


template<typename T>
void bar(const T x)
{
    
    
  {
    
    
    auto obj = foo(x);
    if constexpr(std::is_same_v<decltype(obj), T>) {
    
    
      std::operator<<(std::cout, "foo(x) yields same type\n");
    } else /* constexpr */ {
    
    
      std::operator<<(std::cout, "foo(x) yields different type\n");
    } 
    
  }
  
}
/* First instantiated from: insights.cpp:20 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void bar<int>(const int x)
{
    
    
  {
    
    
    double obj = foo(x);
    if constexpr(false) {
    
    
    } else /* constexpr */ {
    
    
      std::operator<<(std::cout, "foo(x) yields different type\n");
    } 
    
  }
}
#endif

/* First instantiated from: insights.cpp:21 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void bar<double>(const double x)
{
    
    
  {
    
    
    double obj = foo(static_cast<int>(x));
    if constexpr(true) {
    
    
      std::operator<<(std::cout, "foo(x) yields same type\n");
    } 
    
  }
  
}
#endif
int main()
{
    
    
  bar(2);
  bar(2.5);
  return 0;
}

パラメータが type である関数がある場合Tそれが同じ型を返すかどうかに応じて、異なる方法で処理できます。戻り値に基づいて判断したい場合は、次のように記述できます。constexprfoo()foo(x)x
foo(x)

#include <functional>  // for std::forward()
#include <iostream>
#include <type_traits>  // for std::is_same<> and std::invoke_result<>

using namespace std;

constexpr int foo(int x) {
    
     return x * 2; }

void bar() {
    
    
    constexpr auto c = 2;
    if constexpr (constexpr auto obj = foo(c); obj == 4) {
    
    
        std::cout << "foo("<<"c"<<") ==  " << obj << endl;
    } else {
    
    
        std::cout << "foo(x) yields different type\n";
    }
}
int main() {
    
    
    bar();
    return 0;
}

条件文で値を使用する場合はobjobjそれを として宣言する必要があることに注意してくださいconstexpr操作の結果は次のようになります。

foo(c) ==  4

前処理コードは次のとおりです。

#include <functional>  // for std::forward()
#include <iostream>
#include <type_traits>  // for std::is_same<> and std::invoke_result<>

using namespace std;

inline constexpr int foo(int x)
{
    
    
  return x * 2;
}

void bar()
{
    
    
  constexpr const int c = 2;
  {
    
    
    constexpr const int obj = foo(c);
    if constexpr(true) {
    
    
      std::operator<<(std::operator<<(std::operator<<(std::cout, "foo("), "c"), ") ==  ").operator<<(obj).operator<<(std::endl);
    } else /* constexpr */ {
    
    
      std::operator<<(std::cout, "foo(x) yields different type\n");
    } 
  }
}
int main()
{
    
    
  bar();
  return 0;
}

テンプレートの外でコンパイル時間を使用するif

if constexprテンプレートだけでなく、あらゆる関数で使用できますbool条件式がコンパイル時に型に変換可能である限り。ただし、 then 部分else部分のすべてのステートメントは、破棄されてもよい場合でも、通常の関数で使用する場合には有効でなければなりません

たとえば、次のコードは、符号付きの値によってelse部分が破棄される場合undeclared()でも、 への呼び出しが有効である必要があるため、コンパイルされません。char

#include <limits>

template<typename T>
void foo(T t);

int main()
{
    
    
    if constexpr(std::numeric_limits<char>::is_signed) {
    
    
        foo(42);        // OK
    }
    else {
    
    
        undeclared(42); // 未声明时总是ERROR(即使被丢弃)
    }
}

次のコードも、失敗する静的アサーションが常に存在するため、正常にコンパイルされることはありません。

if constexpr(std::numeric_limits<char>::is_signed) {
    
    
    static_assert(std::numeric_limits<char>::is_signed);
}
else {
    
    
    static_assert(!std::numeric_limits<char>::is_signed);
}

汎用コードの外側でコンパイル時に使用するif唯一の利点は、破棄された部分が最終プログラムの一部にならず、結果として得られる実行可能プログラムのサイズが小さくなることですたとえば、次のようなプログラムです。

#include <limits>
#include <string>
#include <array>

int main()
{
    
    
    if (!std::numeric_limits<char>::is_signed) {
    
    
        static std::array<std::string, 1000> arr1;
        ...
    }
    else {
    
    
        static std::array<std::string, 1000> arr2;
        ...
    }
}

どちらかが最終的な実行可能ファイルarr1の一部になりますarr2が、両方が含まれるわけではありません。

参考

http://www.cppstd17.com/

おすすめ

転載: blog.csdn.net/MMTS_yang/article/details/130772938