yohhoyの日記

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

リテラル0との比較のみ許容する型

リテラル0との比較のみ許容する型の作り方。そのような型として、C++2a(C++20)三方比較演算子<=>の戻り値型(partial_ordering/ weak_ordering/strong_ordering)がある。

// C++2a
#include <cassert>
#include <compare>

int main()
{
  std::strong_ordering r = (108 <=> 42);
  assert(r > 0);  // OK
  assert(r > 1);  // NG: コンパイルエラー
}

リテラル0がstd::nullptr_t型の値(nullptr)に暗黙変換されることを利用する。

N4861 17.11.2.1/p1, p3より一部引用。

1 The types partial_ordering, weak_ordering, and strong_ordering are collectively termed the comparison category types. (snip)

3 The relational and equality operators for the comparison category types are specified with an anonymous parameter of unspecified type. This type shall be selected by the implementation such that these parameters can accept literal 0 as a corresponding argument. [Example: nullptr_t meets this requirement. --end example] In this context, the behavior of a program that supplies an argument other than a literal 0 is undefined.

関連URL

requires式から利用可能な宣言

C++2a(C++20) コンセプト requires式(requires-expression) では、同式を包含するコンテキストのあらゆる宣言を利用できる。

下記コードのrequires式からは関数テンプレートの仮引数xを参照している。requires式の本体(requirement-body)は評価されず(unevaluated)、型情報(型T)のみを利用している。

#include <iostream>
#include <string>
using namespace std::string_literals;

template <typename T>
T doubling(T x)
{
  if constexpr (requires { x.doubling(); }) {
    x.doubling();  // #1
    return x;
  }
  else if constexpr (requires { x * 2; }) {
    return x * 2;  // #2
  }
  else {
    return x + x;  // #3
  }
}

// doublingメンバ関数を持つクラス型
struct X {
  unsigned m_;
  void doubling()
    { m_ <<= 1; }
  friend std::ostream& operator<<(std::ostream& os, const X& x)
    { return os << "X{" << x.m_ << "}"; }
};

std::cout << doubling(3.14);    // 6.28   (#2)
std::cout << doubling("abc"s);  // abcabc (#3)
std::cout << doubling(X{21});   // X{42}  (#1)

1番目constexpr if文の条件式記述では下記の選択肢が考えられる:

  • requires { x.doubling(); }:関数パラメータ宣言を利用
  • requires (T t) { t.doubling(); }:ローカルパラメータ導入と利用
  • requires { T{}.doubling(); }:テンプレートパラメータを利用

N4861 7.5.7/p1-2, 4-5より一部引用(下線部は強調)。

1 A requires-expression provides a concise way to express requirements on template arguments that can be checked by name lookup (6.5) or by checking properties of types and expressions.
 requires-expression:
   requires requirement-parameter-listopt requirement-body
 requirement-parameter-list:
   ( parameter-declaration-clauseopt )
 requirement-body:
   { requirement-seq }
 (snip)
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).

4 A requires-expression may introduce local parameters using a parameter-declaration-clause (9.3.3.5). (snip)
5 The requirement-body contains a sequence of requirements. These requirements may refer to local parameters, template parameters, and any other declarations visible from the enclosing context.

関連URL:

ラムダ式のオーバーロード

プログラミング言語C++において、ラムダ式オーバーロード(もどき)を実装する方法。

// C++17
template <typename... Ts>
struct overloaded : Ts... {
  // 基底クラス(ラムダ式のクロージャクラス)が提供する
  // operator()群をoverloadedクラス自身から公開する
  using Ts::operator()...;
};

// テンプレート引数Ts...の推論ガイド宣言
template <typename... Ts>
overloaded(Ts...) -> overloaded<Ts...>;

// 文字列(const char*), doube型, それ以外でオーバーロード
auto printer = overloaded{
  [](const char* s) { std::cout << std::quoted(s) << '\n'; },
  [](double v) { std::cout << std::fixed << v << '\n'; },
  [](auto x) { std::cout << x << '\n'; }
};

printer("Hi");  // "Hi"
printer(3.14);  // 3.140000
printer(42);    // 42

C++17現在、集成体(aggregate)に対しては推論ガイド(deduction guide)の宣言が必要となる。(PDF)P1816R0採択によりC++2a(C++20)から推論ガイドが暗黙に生成されるようになる(C++17同様に明示宣言してもよい)。

// C++2a
template <typename... Ts>
struct overloaded : Ts... {
  using Ts::operator()...;
};
// 下記の推論ガイドが暗黙宣言される
// template <typename... Ts> overloaded(Ts...) -> overloaded<Ts...>;

関連URL

yield式を使わないジェネレータ

C++2a(C++20)コルーチンにはジェネレータ実装を容易にするco_yield式が導入されるが、動作仕様的にはco_await式のシンタックスシュガーとなっている。

#include <coroutine>
#include <iostream>
#include <utility>

#define MIMIC_CO_YIELD 1

#if MIMIC_CO_YIELD
// yield式相当を表現する値保持クラス
template <typename T> struct yield { T value; };
#endif

template <typename T>
struct generator {
  struct promise_type {
    T value_;
    auto get_return_object() { return generator(*this); }
    auto initial_suspend() noexcept { return std::suspend_always{}; }
    auto final_suspend() noexcept { return std::suspend_always{}; }
#if MIMIC_CO_YIELD
    // 式co_await yield{v}に対応するカスタイマイズポイント
    auto await_transform(yield<T>&& bag) {
      value_ = std::move(bag.value);
      return std::suspend_always{};
    }
#else
    auto yield_value(T x) {
      value_ = std::move(x);
      return std::suspend_always{};
    }
#endif
    void return_void() {}
    void unhandled_exception() { throw; }
  };
  using coro_handle = std::coroutine_handle<promise_type>;

  generator(generator const&) = delete;
  generator(generator&& rhs) : coro_(rhs.coro_) { rhs.coro_ = nullptr; }
  ~generator() { if (coro_) coro_.destroy(); }
 
  bool next() { return coro_ ? (coro_.resume(), !coro_.done()) : false; }
  T& value() { return coro_.promise().value_; }

private:
  generator(promise_type& p)
    : coro_(coro_handle::from_promise(p)) {}
  coro_handle coro_;
};

generator<int> f()
{
#if MIMIC_CO_YIELD
  // co_await式を利用
  co_await yield{1};
  co_await yield{2};
#else
  // 通常のco_yield式
  co_yield 1;
  co_yield 2;
#endif
}

int main() {
  auto g = f();
  while (g.next()) {
    std::cout << g.value() << std::endl;
  }
}

2022-01-13追記:co_awaitco_yieldでは演算子の優先順位が異なるため*1、いつでも両者を相互変換できるわけではない。co_awaitは単項演算子と同じく優先順位が高く、co_yieldは代入演算子と同じく優先順位が低い。*2

C++2a WD(n4861) 7.6.17/p1より一部引用。

A yield-expression shall appear only within a suspension context of a function (7.6.2.3). Let e be the operand of the yield-expression and p be an lvalue naming the promise object of the enclosing coroutine (9.5.4), then the yield-expression is equivalent to the expression co_await p.yield_value(e).
(snip)

関連URL

C++20標準ライブラリ仕様:Constraints/Mandates/Preconditions

C++2a(C++20)標準ライブラリの関数仕様記述で用いられる Constraints/Mandates/Preconditions の違いについてメモ。

  • 現行C++17標準ライブラリの Requires は廃止され、C++2aでは Constraints/Mandates/Preconditions に細分化される。
  • Mandates: 型や定数式に対する必須要件。違反時は ill-formed のためコンパイルエラー。
  • Constraints: 型や定数式に対する制約条件。違反時は関数オーバーロード候補から除外される。“SFINAE-friendly”
  • Preconditions: 引数値やオブジェクト状態に対する事前条件。違反時は実行時エラーや未定義動作(undefined behavior)を引き起こす。
  • C++標準規格では関数仕様のみを定め、標準ライブラリの実現方式には言及しない。
    • 例:Constraintsは requires節*1enable_if*2、constexpr if*3 など任意の仕組みで実現されうる。

例:std::packaged_task<R(ArgTypes...)>のテンプレートコンストラクtemplate<class F> packaged_task(F&& f);定義は、C++17/C++2a標準ライブラリ仕様ではそれぞれ下記の通り記述される。*4

C++17仕様)
Requires: INVOKE<R>(f, t1, t2, ..., tN), where t1, t2, ..., tN are values of the corresponding types in ArgTypes..., shall be a valid expression. Invoking a copy of f shall behave the same as invoking f.
Remarks: This constructor shall not participate in overload resolution if decay_t<F> is the same type as packaged_task<R(ArgTypes...)>.

C++2a仕様)
Constraints: remove_cvref_t<F> is not the same type as packaged_task<R(ArgTypes...)>.
Mandates: is_invocable_r_v<R, F&, ArgTypes...> is true.
Preconditions: Invoking a copy of f behaves the same as invoking f.

提案文書(PDF)P0788R3, §3 Proposed principles and practices より一部引用(下線部は強調)。注:C++2aではContracts導入が見送られたため*5Expects:Ensures: はそれぞれ Preconditions:Postconditions: が対応する。

I. Let's not recycle a Requires: element to mean something other than what it means today.

  • a) Let's instead adopt new elements, described below, to specify the Library requirements that are (or that should have been) specified via our current Requires: elements. [(snip)]
  • (snip)


II. Let's introduce a new Constraints: element.

  • a) Let's use this Constraints: element to specify the compile-time circumstances that must be satisfied in order that the corresponding Library component will be compiled. [(snip)]
  • b) Let's ensure that unsatisfied Constraints: not produce any diagnostic in and of themselves.
    [This obviates the need for specification wording such as "shall not participate in overload resolution." Note that a consequential diagnostic might still result: for example, overload resolution might find no viable candidates due to unsatisfied constraints and/or other factors.]
  • c) Let's introduce a new Mandates: element to specify the compile-time circumstances under which, when unsatisfied, an implementation must produce a diagnostic.
    [(snip)]
    [This element obviates the need for any "is ill-formed" specifications. For example, in [pair.astuple]/1 we today find the specification "Requires: I < 2. The program is ill-formed if I is out of bounds." Under the present proposal, this would be simplified to "Mandates: I < 2."]


III. Let's introduce a new Expects: element.

  • a) Let's use this Expects: element to specify the circumstances that must be satisfied to avoid undefined behavior when the corresponding Library component is invoked.
    [Industry-wide, such requirements have come to be known as preconditions, but the Contracts proposals [P0542R1] seem to have chosen "expects" as their preferred term of art; it seems better to have a single term and use it consistently.]
  • (snip)


IV. Let's avoid any specification that demands any particular technology by which implementations must comply with Library specifications.

  • a) Let's permit an implementation to use a requires-clause, an enable_if, a constexpr if, or any other technology or combination of technologies to meet Constraints: specifications.
  • b) Let's permit an implementation to use static_assert and/or any other technologies to meet Mandates: specifications.
  • c) Let's permit an implementation to use Contracts attributes [P0542R1] and/or any other technologies to meet Expects: and Ensures: specifications.
  • d) Let's consider user code that relies on any specific technology on the part of an implementation to be ill-formed, with no diagnostic required.

C++17仕様

C++17 20.4.1.4/p3-4より一部引用。

3 Descriptions of function semantics contain the following elements (as appropriate):

  • Requires: the preconditions for calling the function
  • (snip)

4 (snip) If F's semantics specifies a Requires: element, then that requirement is logically imposed prior to the equivalent-to semantics. (snip)

C++2a仕様

C++2a DIS(N4861) 16.4.1.4/p3より一部引用。

3 Descriptions of function semantics contain the following elements (as appropriate):

  • Constraints: the conditions for the function's participation in overload resolution (12.4). [Note: Failure to meet such a condition results in the function's silent non-viability. --end note] [Example: An implementation might express such a condition via a constraint-expression (13.5.2). --end example]
  • Mandates: the conditions that, if not met, render the program ill-formed. [Example: An implementation might express such a condition via the constant-expression in a static_assert-declaration (9.1). If the diagnostic is to be emitted only after the function has been selected by overload resolution, an implementation might express such a condition via a constraint-expression (13.5.2) and also define the function as deleted. --end example]
  • Preconditions: the conditions that the function assumes to hold whenever it is called.
  • (snip)

4 (snip) If F's semantics specifies any Constraints or Mandates elements, then those requirements are logically imposed prior to the equivalent-to semantics. (snip)

関連URL

atoi関数のかしこい実装

C標準ライブラリ Muslのatoi関数実装 では、符号付き整数オーバーフロー回避のため負数範囲で10進数値を減算してゆき最後に符号反転を行っている。

int atoi(const char *s)
{
  int n=0, neg=0;
  while (isspace(*s)) s++;
  switch (*s) {
  case '-': neg=1;
  case '+': s++;
  }
  /* Compute n as a negative number to avoid overflow on INT_MIN */
  while (isdigit(*s))
    n = 10 * n - (*s++ - '0');
  return neg ? n : -n;
}

C言語の符号付き整数型(int)では “2の補数” 表現が用いられるため*1、最大値INT_MAXより最小値INT_MINの絶対値が 1 だけ大きくなり(例:16bit幅ならINT_MAX = 32767, INT_MIN = -32768)、正数範囲の累積計算では値-INT_MINを表現できず未定義動作(undefined behavior)を引き起こしてしまう。*2

その他のC標準ライブラリ実装(glibc, BSD libc, Newlib)では、単純にstrtol関数へ実装移譲している。

int atoi (const char *s)
{
  return (int) strtol (s, NULL, 10);
}

ノート:int型よりlong型の値域が広い場合strtol関数戻り値キャストで未定義動作(undefined behavior)となるが、そもそもC標準ライブラリatoi関数仕様では「戻り値がint型の値域外となる場合の動作は未定義(the behavior is undefined)」とされるため何ら問題はない。いいね?

  • 2022-01-08追記:longint型変換において変換先の符号付き整数型で表現きない値の場合、処理系定義(implementation-defined)の値が得られるか処理系定義のシグナルが発生する(thanks to emkさんコメント)。

C99 3.4.3/p3, 7.20.1/p1, 7.20.1.4/p8より引用(下線部は強調)。

EXAMPLE An example of undefined behavior is the behavior on integer overflow.

The functions atof, atoi, atol, and atoll need not affect the value of the integer expression errno on an error. If the value of the result cannot be represented, the behavior is undefined.

The strtol, strtoll, strtoul, and strtoull functions return the converted value, if any. If no conversion could be performed, zero is returned. If the correct value is outside the range of representable values, LONG_MIN, LONG_MAX, LLONG_MIN, LLONG_MAX, ULONG_MAX, or ULLONG_MAX is returned (according to the return type and sign of the value, if any), and the value of the macro ERANGE is stored in errno.

関連URL

*1:C17現在は “1の補数(ones' complement)” または “符号と絶対値(sign and magnitude)” 表現も許容されるが、次期規格C2xでは “2の補数(two's complement)” のみ許容と改定される。提案文書(PDF)N2412参照。

*2:符号付き整数オーバーフローは、標準規格での明示的な定義が無いことをもって未定義動作とされる(C99 4/p2)

関数型への参照型にまつわる特例ルール

プログラミング言語C++の関数型(function type)には左辺値(lvalue)しか存在しないが(→id:yohhoy:20200530)、左辺値参照型(R (&)(Args...))/右辺値参照型(R (&&)(Args...))いずれにも束縛できる。そう、よかったね。

#include <utility>
using std::move;  // 右辺値参照型(T&&)を返す関数

void f() {}
// 関数型void()
void ( &lref1)() = f;        // OK
void (&&rref1)() = f;        // OK: 右辺値参照型だがlvalue束縛可能
void ( &lref2)() = move(f);  // OK: 関数型の式move(f)はlvalue
void (&&rref2)() = move(f);  // OK: 右辺値参照型だがlvalue束縛可能
// (関数型のprvalueは存在しない)

int n = 42;
// 通常の型(int)
int&  lref3 = i;        // OK
int&& rref3 = i;        // NG: 右辺値参照型はlvalue束縛不可
int&  lref4 = move(i);  // NG: 左辺値参照型はxvalue束縛不可
int&& rref4 = move(i);  // OK
int&  lref5 = 42;       // NG: 左辺値参照型はprvalue束縛不可
int&& rref5 = 42;       // OK

関数オーバーロード解決では関数型への左辺値参照型が優先されるため、あいまいさは存在しない。(C++17 16.3.3.2/p3)

C++17 11.6.3/p5より一部引用(下線部は強調)。

A reference to type "cv1 T1" is initialized by an expression of type "cv2 T2" as follows:

  • If the reference is an lvalue reference and the initializer expression
    • is an lvalue (but is not a bit-field), and "cv1 T1" is reference-compatible with "cv2 T2", or
    • (snip)
  • Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference shall be an rvalue reference. (snip)
    • If the initializer expression
      • is an rvalue (but not a bit-field) or function lvalue and "cv1 T1" is reference-compatible with "cv2 T2", or
      • (snip)