yohhoyの日記

技術的メモをしていきたい日記

OpenMP 5.1仕様リリース

2020年11月 OpenMP 5.1仕様リリース記事 OpenMP ARB releases OpenMP 5.1 with Vital Usability Enhancements より抄訳。

OpenMP仕様バージョン5.1はOpenMP ARB、主要なコンピュータハードウェア/ソフトウェアベンダのグループ、そしてOpenMPコミュニティのユーザによって共同開発されました。改訂仕様は下記の主要な機能追加を含みます:

アクセラレータデバイス相互作用の改善:

コンパイラ最適化の改善を可能とする多くの情報提供:

  • assumeディレクティブによってプログラムのOpenMP利用方法に関する追加情報を処理系へと指示でき、コンパイラによって安全に適用できる追加の最適化や、安全なOpenMPサブセット実装が可能となります。*2
  • データ局所性を向上させるtileディレクティブから始まり、ループを完全または部分的に展開するunrollディレクティブと、ループ変形ディレクティブが追加されます。

スレッド実行のフィルタリング:

  • maskedディレクティブによりコード区間の実行をスレッド・サブセットに制限できます。

ユーザによるコンパイル時エラー・警告生成の許可:

  • 新しいerrorディレクティブが追加されます。

モダンC++におけるOpenMP利用の改善:

  • 旧来のpragma形式に代わって、OpenMPディレクティブ指定にC++属性構文を利用できるようになり、テンプレートとの統合がシンプルになります。*3
  • C11, C18, C++11, C++14, C++17, C++20のフルサポート:これらの言語で書かれたプログラムはOpenMP並列化可能です。

Fortranサポートの改善:

  • Fortran 2008が完全にポートされ、Fortran 2018への初期サポートが追加されます。

環境固有な関数宣言の簡易化:

  • 単一declare variantコンストラクトに複数関数を宣言可能となり、コードを特定環境により一層合わせられます。

関連URL

*1: (PDF)Additional Definitions (v2.0)によれば次の外部ランタイム環境が定義される:NVIDIA CUDA, Khronos OpenCL, Khronos SYCL, AMD ROCm HIP, Intel oneAPI Level Zero

*2:訳注:“対象コード区間入れ子OpenMPディレクティブを含まない” といった最適化ヒント情報を表明できる。

*3:訳注:名前空間 omp 以下に directive と sequence の2属性が追加される。for構文に omp parallel と omp for ディレクティブを指定する例:[[omp::sequence( directive(parallel), directive(for) )]] for (/*...*/) {} 積極的に使いたくなる構文ではない('A`)...

制約式std::bool_constant<cond>::value

C++20 制約式(constraint-expression)でテンプレートパラメータに依存する定数条件式condを表現する場合、非定数式に起因するハードエラーを防ぐためstd::bool_constant<cond>::valueと記述する必要がある。

#include <type_traits>

struct X {
  // X::valueは非定数式
  static inline int value = 42;
};
struct Y {
  // Y::valueは定数式
  static constexpr inline int value = 42;
};
struct Z {};

template<typename T>
concept C0 = (T::value == 42);
static_assert(!C0<X>);  // NG: ill-formed
static_assert( C0<Y>);  // OK
static_assert(!C0<Z>);  // OK

template<typename T>
concept C1 = std::bool_constant<T::value == 42>::value;
static_assert(!C1<X>);  // OK
static_assert( C1<Y>);  // OK
static_assert(!C1<Z>);  // OK

C++標準ライブラリではuniform_random_bit_generatorコンセプトで同テクニックが利用される。C++20 26.6.2.3/p1より引用。

A uniform random bit generator g of type G is a function object returning unsigned integer values such that each value in the range of possible results has (ideally) equal probability of being returned. [Note: The degree to which g's results approximate the ideal is often determined statistically. --end note]

template<class G>
  concept uniform_random_bit_generator =
    invocable<G&> && unsigned_integral<invoke_result_t<G&>> &&
    requires {
      { G::min() } -> same_as<invoke_result_t<G&>>;
      { G::max() } -> same_as<invoke_result_t<G&>>;
      requires bool_constant<(G::min() < G::max())>::value;
    };

関連URL

2進数リテラル in 標準C

プログラミング言語Cの次期仕様C2xでは 2進数リテラル(binary literal) が正式仕様となる。そこ今更とか言わない。

// C2x
unsigned x = 0b101010;
unsigned y = 0B11110000;

ノート:2003年時点の (PDF) Rationale for International Standard Programming Languages Cでは下記の通り否定的だったが、C++14で2進数リテラルが導入されたことも影響していそう。

6.4.4.1 Integer constants
A proposal to add binary constants was rejected due to lack of precedent and insufficient utility.

関連URL

std::is_convertible vs. std::convertible_to

C++標準ライブラリstd::is_convertibleメタ関数とstd::convertible_toコンセプトの超微妙な違い。本記事の内容はStackOverflowで見つけた質問と回答に基づく。

要約:

  • is_convertible<From, To>メタ関数:From型からTo型へ暗黙変換できることを検査する。
  • convertible_to<From, To>コンセプト:From型からTo型へ暗黙変換および明示変換できることを検査する。

暗黙変換は可能だが明示変換が許可されないケースで差異が生じる。ジェネリックライブラリの設計者大変すぎでしょ。

#include <concepts>
#include <type_traits>

struct From;
struct To {
  To() = default;
  // From→Toの明示変換を禁止
  explicit To(From) = delete;
};
struct From {
  // From→Toの暗黙変換は許可
  operator To() { return {}; }
};

static_assert( std::is_convertible_v<From, To> );
static_assert( !std::convertible_to<From, To> );

To to1 = From{};  // OK: 暗黙変換
To to2{From{}};   // NG: 明示変換

C++20(N4861) 18.4.4/p1, 20.15.6/p5より引用(下線部は強調)。

Given types From and To and an expression E such that decltype((E)) is add_rvalue_reference_t<From>, convertible_to<From, To> requires E to be both implicitly and explicitly convertible to type To. The implicit and explicit conversions are required to produce equal results.

template<class From, class To>
  concept convertible_to =
    is_convertible_v<From, To> &&
    requires(add_rvalue_reference_t<From> (&f)()) {
      static_cast<To>(f());
    };

The predicate condition for a template specialization is_convertible<From, To> shall be satisfied if and only if the return expression in the following code would be well-formed, including any implicit conversions to the return type of the function:

To test() {
  return declval<From>();
}

[Note: This requirement gives well-defined results for reference types, void types, array types, and function types. -- end note] Access checking is performed in a context unrelated to To and From. Only the validity of the immediate context of the expression of the return statement (8.7.3) (including initialization of the returned object or reference) is considered. [Note: The initialization can result in side effects such as the instantiation of class template specializations and function template specializations, the generation of implicitly-defined functions, and so on. Such side effects are not in the "immediate context" and can result in the program being ill-formed. -- end note]

関連URL

非staticデータメンバを判定する制約式

C++20 requires式の単純な利用では非static/staticメンバを区別できない。requires式の本体部は評価されない(unevaluated)ため、通常コードとは異なる規則が適用されることに注意。

// staticメンバmを持つ型X
struct X {
  static const int m = 1;
};

// (非static)メンバmを持つ型Y
struct Y {
  int m = 2;
};

// staticメンバT::mを確認するコンセプト(?)
template <typename T>
concept HasStaticM = requires {
    { T::m } -> std::convertible_to<int>;
  };

// 式 T::m はmがstaticメンバのときのみ有効
assert( X::m == 1 );  // OK
static_assert( HasStaticM<X> );  // OK

// mが非staticメンバの場合は式 T::m と書けないが...
assert( Y::m == 2 );  // NG: ill-formed
static_assert( HasStaticM<Y> );  // OK !?

T::mが非static/staticデータメンバのいずれかを判定するには、&T::mの型をstd::is_member_object_pointerメタ関数に通す。

  • X::mはstaticデータメンバのため、式&X::mは通常のポインタ型(int*)となる。
  • Y::mは非staticデータメンバのため、式&Y::mはデータメンバへのポインタ型(int X::*)となる。
template <typename T>
concept HasStaticM = requires {
    { T::m } -> std::convertible_to<int>;
    requires !std::is_member_object_pointer_v<decltype(&T::m)>;
  }; 

static_assert(  HasStaticM<X> );  // OK
static_assert( !HasStaticM<Y> );  // OK

C++20(N4861) 7.5.4/p2, 7.5.7/p2より引用(下線部は強調)。

id-expression:
   unqualified-id
   qualified-id


2 An id-expression that denotes a non-static data member or non-static member function of a class can only be used:

  • as part of a class member access (7.6.1.4) in which the object expression refers to the member's class or a class derived from that class, or
  • to form a pointer to member (7.6.2.1), or
  • if that id-expression denotes a non-static data member and it appears in an unevaluated operand. [Example:
struct S {
  int m;
};
int i = sizeof(S::m); // OK
int j = sizeof(S::m + 42); // OK

-- end example]

2 A requires-expression is a prvalue of type bool whose value is described below. Expressions appearing within a requirement-body are unevaluated operands (7.2).

関連URL

"Poison-pill" overload for CPO

C++20標準ライブラリで導入された Customization Point Object (CPO)定義で必要となる Poison-pill*1 オーバーロードについてメモ。std::ranges::swapstd::ranges::begin/endなどのCPO定義で利用される。

本記事の内容はStackOverflowで見つけた質問と回答に基づく。

std::ranges名前空間でのCPO定義位置からは、親名前空間stdで定義されるカスタマイズポイント同名の制約のない関数テンプレート(std::swapstd::begin/end)が "見えて” しまうため、同関数テンプレートシグネチャをdelete宣言してオーバーロード候補から除外する(★印)。

// std::ranges::swap CPO実装イメージ(超簡略化版)
namespace std::ranges {
  namespace swap_impl {
    template<class T> void swap(T&, T&) = delete;  // ★

    struct swap_fn {
      template<class T1, class T2>
        requires /* C++20 18.4.9/p2/b1 */
      constexpr void operator()(T1& e1, T2& e2) const {
        // 非修飾名・ADL経由でカスタマイズポイント(swap)を呼び出す
        swap(e1, e2);
      }
      // ...
    };
  }

  // std::ranges::swap CPO定義
  inline namespace swap_cpo {
    inline constexpr swap_impl::swap_fn swap{};
    // Hidden friendとの名前衝突回避のためインライン名前空間が必要
    // 詳細説明は提案文書 P1895R0 を参照のこと
  }
}

C++20 Rangesライブラリの前身、Ranges TS検討当時の提案文書 P0370R3 Ranges TS Design Updates Omnibus より一部引用。

unqualified name lookup for the name swap could find the unconstrained swap in namespace std either directly - it’s only a couple of hops up the namespace hierarchy - or via ADL if std is an associated namespace of T or U. If std::swap is unconstrained, the concept is "satisfied" for all types, and effectively useless. The Ranges TS deals with this problem by requiring changes to std::swap, a practice which has historically been forbidden for TSs. Applying similar constraints to all of the customization points defined in the TS by modifying the definitions in namespace std is an unsatisfactory solution, if not an altogether untenable.


We propose a combination of the approach used in N4381 with a "poison pill" technique to correct the lookup problem. Namely, we specify that unqualified lookup intended to find user-defined overloads via ADL must be performed in a context that includes a deleted overload matching the signature of the implementation in namespace std. E.g., for the customization point begin, the unqualified lookup for begin(E) (for some arbitrary expression E) is performed in a context that includes the declaration void begin(const auto&) = delete;. This "poison pill" has two distinct effects on overload resolution. First, the poison pill hides the declaration in namespace std from normal unqualified lookup, simply by having the same name. Second, for actual argument expressions for which the overload in namespace std is viable and found by ADL, the poison pill will also be viable causing overload resolution to fail due to ambiguity. The net effect is to preclude the overload in namespace std from being chosen by overload resolution, or indeed any overload found by ADL that is not more specialized or more constrained than the poison pill.

C++20(N4861) 16.4.2.2.6/p6より引用(下線部は強調)。

[Note: Many of the customization point objects in the library evaluate function call expressions with an unqualified name which results in a call to a program-defined function found by argument dependent name lookup (6.5.2). To preclude such an expression resulting in a call to unconstrained functions with the same name in namespace std, customization point objects specify that lookup for these expressions is performed in a context that includes deleted overloads matching the signatures of overloads defined in namespace std. When the deleted overloads are viable, program-defined overloads need be more specialized (13.7.6.2) or more constrained (13.5.4) to be used by a customization point object. -- end note]

関連URL