yohhoyの日記

2018-07-03

C++標準ライブラリのビット処理関数群

C++2a(C++20)標準ライブラリに追加される <bit> ヘッダについてメモ。

// C++2a <bit>ヘッダ
namespace std {
  // sizeof(To) == sizeof(From)
  template<typename To, typename From>
    constexpr To bit_cast(const From& from) noexcept;

  // T = 符号なし整数型
  template <class T>
    constexpr bool ispow2(T x) noexcept;
  template <class T>
    constexpr T ceil2(T x) noexcept;
  template <class T>
    constexpr T floor2(T x) noexcept;
  template <class T>
    constexpr T log2p1(T x) noexcept;
}
関数効果
bit_cast<To>(n)ビット表現を維持した型キャスト(安全なreinterpret_cast)
ispow2(n)nが"2の冪乗"か否かを判定
ceil2(n)n以上で最小の"2の冪乗"
floor2(n)n以下で最大の"2の冪乗"
log2p1(n)1 + floor(log2(n))の整数演算*1

入力値nと関数戻り値(n=0..16):

nispow2ceil2floor2log2p1
0false100
1true111
2true222
3false422
4true443
5..7false843
8true884
9..15false1684
16true16165

関連URL

*1:初期提案P0556R0では“前提条件(Requires) n>0 あり log2(n)”と定義されていたが、C++標準ライブラリの広い契約(Wide Contracts)を満たす(→id:yohhoy:20180127)よう仕様変更された。

2018-06-30

名無しの変数

プログラミング言語C++には、言語仕様の定義上“名前のない変数(variable)”が存在する。誰得情報。

try {
} catch (std::exception&) {  // ★
}

C++14 3/p6より引用(下線部は強調)。C++17では6/p6。

A variable is introduced by the declaration of a reference other than a non-static data member or of an object. The variable's name, if any, denotes the reference or object.

同Wordingを導入したCWG DR 1769より一部引用。

Additional discussion has pointed out that, although the unnamed handler parameter is no longer called a "temporary" in the proposed resolution, 15.2 [class.temporary] paragraph 1 still lists "entering a handler (18.3 [except.handle])" as one of the contexts in which a temporary is created. A question was also raised as to whether it is necessary to deal with named and unnamed handler parameters separately, since both are now referred to as "variables" in the revised wording. This issue has therefore been returned to "review" status to allow consideration of these points.

関連URL

2018-06-22

2018-06-09

条件付きexplicit指定子

C++2a(C++20)言語仕様では 条件付きexplicit指定子 が追加され、pairやtupleなどの型Tを値保持するクラステンプレートにおいて、型Tコンストラクタ実装時のexplicit性継承(→id:yohhoy:20150416)が容易になる。

// C++17仕様
template <typename T1, typename T2>
struct pair {
  // non-explicitコンストラクタ
  template <typename U1=T1, typename U2=T2,
    std::enable_if_t<
      std::is_constructible_v<T1, U1> &&
      std::is_constructible_v<T2, U2> &&
      std::is_convertible_v<U1, T1> &&
      std::is_convertible_v<U2, T2>
    , int> = 0>
  constexpr pair(U1&&, U2&&);
  
  // explicitコンストラクタ
  template <typename U1=T1, typename U2=T2,
    std::enable_if_t<
      std::is_constructible_v<T1, U1> &&
      std::is_constructible_v<T2, U2> &&
      !(std::is_convertible_v<U1, T1> &&
        std::is_convertible_v<U2, T2>)
     , int> = 0>
  explicit constexpr pair(U1&&, U2&&);
};
// C++2a仕様: Concept+explicit(bool)
template <typename T1, typename T2>
struct pair {
  // explicit指定有無はT1, T2, U1, U2型に依存
  template <typename U1=T1, typename U2=T2>
    requires std::is_constructible_v<T1, U1> &&
             std::is_constructible_v<T2, U2>
  explicit(!std::is_convertible_v<U1, T1> ||
           !std::is_convertible_v<U2, T2>)
  constexpr pair(U1&&, U2&&);
};

関連URL

2018-06-04

配列有効範囲外を指すポインタ値は存在が許されない

プログラミング言語C/C++における、配列型とポインタ演算の知られざる*1落とし穴。

問題:下記sum_odd関数の実行結果は?

int data[5] = { 1, 2, 3, 4, 5 };

// 奇数番目の要素値のみを合計する
int sum_odd()
{
  int s = 0;
  int *endp = data + 5;  // 配列末尾要素の次位置

  // ポインタ値p が endp を越えるまでループ処理
  for (int *p = data; p < endp; p += 2) {
    s += *p;
  }
  return s;
}

答え:未定義動作(undefined behavior)。C/C++こわい(((;´・ω・`))) これがC/C++だ( ・`ω・´)キリッ。

C/C++では、ポインタ値は “有効な配列要素”*2 もしくは “配列末尾要素の次位置(one past the last element)” しか指してはならない。それ以外の値、例えば前掲コードのように配列末尾要素の2つ後ろ(のつもり)などは、加減算によりポインタ値が算出された時点で未定義動作と規定される。

C

C11 6.5.6/p8より引用(下線部は強調)。C99でも6.5.6/p8。

8 When an expression that has integer type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integer expression. In other words, if the expression P points to the i-th element of an array object, the expressions (P)+N (equivalently, N+(P)) and (P)-N (where N has the value n) point to, respectively, the i+n-th and i-n-th elements of the array object, provided they exist. Moreover, if the expression P points to the last element of an array object, the expression (P)+1 points one past the last element of the array object, and if the expression Q points one past the last element of an array object, the expression (Q)-1 points to the last element of the array object. If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined. If the result points one past the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated.


C++

C++17 8.7/p4より引用(下線部は強調)。C++14/11/03では5.7/p5。

4 When an expression that has integral type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the expression P points to element x[i] of an array object x with n elements, the expressions P + J and J + P (where J has the value j) point to the (possibly-hypothetical) element x[i + j] if 0 ≤ i + jn; otherwise, the behavior is undefined. Likewise, the expression P - J points to the (possibly-hypothetical) element x[i - j] if 0 ≤ i - jn; otherwise, the behavior is undefined.

関連URL

*1:要出典。C言語 == 高級アセンブラ という認識では危ないというお話。

*2:ポインタ値が指す先は、当然 int など非配列のデータ型オブジェクトでも良い。