yohhoyの日記

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

C++オブジェクト基本操作:Copyable/Movable/Swappable

プログラミング言語C++のオブジェクトに対する基本操作として、コピー可能(copyable)/ムーブ可能(movable)/交換可能(swappable)の3段階が存在する。

C++における最も原始的なオブジェクト操作は交換(swap)であり、ムーブ(move)、コピー(copy)の順にオブジェクト型に対する要件が拡張される関係にある。*1

  • 交換可能(swappable):2つの変数間で「値の入れ替え」を行える。(例:無効値表現を持たないオブジェクト*2
  • ムーブ可能(movable):交換可能かつ、別の変数へ「値の移動」を行える。(例:std::unique_ptr<T>
  • コピー可能(copyable):ムーブ可能かつ、別の変数へ「値の複製」を行える。(例:std::string

注:交換操作はムーブ操作を用いても実装可能だが、常にムーブ操作を必要とするわけではない(swapメンバ関数で十分)。

この3操作の関係性は、C++標準ライブラリ提供コンセプト定義における包摂関係(subsumption relation)としても表現されている(→id:yohhoy:20190903)。C++20 18.4.9/p4, 18.6/p1より一部引用。

template<class T>
  concept swappable = requires(T& a, T& b) { ranges::swap(a, b); };
// (snip)

[Note: The semantics of the swappable and swappable_with concepts are fully defined by the ranges::swap customization point. -- end note]

This subclause describes concepts that specify the basis of the value-oriented programming style on which the library is based.

template<class T>
  concept movable =
    is_object_v<T> && move_constructible<T> &&
    assignable_from<T&, T> && swappable<T>;

template<class T>
  concept copyable =
    copy_constructible<T> && movable<T> && assignable_from<T&, T&> &&
    assignable_from<T&, const T&> && assignable_from<T&, const T>;
// (snip)

関連URL

*1:ムーブ可能だが交換不可能な型、コピー可能だがムーブ不可能な型はC++のセマンティクス上存在しえない。

*2:C++標準ライブラリでは “交換可能だがムーブ不可能な型” は提供されない。例えばムーブコンストラクタ・代入演算子をdelete宣言かつ swap 非メンバ関数のみ提供する型が該当する。https://wandbox.org/permlink/3IebVhspiFU3kgQU

"Dreams come" == true

プログラミング言語PHPにおける奇妙な型変換 "Type Juggling"・第2弾。真偽値との比較にも要注意。

<?php
var_dump("Dreams come" == true);
// bool(true)

var_dump("Dreams come" === true);
// bool(false)
?>

Type Juggling規則により「片方がbool型の場合は両辺をbool型として比較評価」する。

関連URL

演算子オーバーロード for 日付リテラル

C++20標準ヘッダ <chrono> カレンダー(Calendar)ライブラリが提供する、日付リテラル表記用の/演算子オーバーロード一覧。

ノート:年月日順で日付リテラルを述する場合、少なくとも年(year)フィールドは常に型を明示した方がトラブルリスク*1が小さい。/演算子は左結合となることをお忘れなく。

#include <chrono>
using namespace std::chrono;

// 2022-06-13
auto date1 = 2022y/6/13;    // OK: year_month_day{year{2022}, month{6}, day{13}}
auto date2 = 2022/June/13;  // NG: ill-formed
auto date3 = 2022/6/13d;    // NG: month_day{month{337}, day{13}}
auto date4 = 2022/6/13;     // NG: int{25}

auto date2m = 2022/(June/13);  // OK: 意図通りに解釈されはするものの
auto date2d = 2022/(6/13d);    // OK: これらの難解表記は避けるべき…

年月日指定:*2

左辺 右辺 結果 表記
year month year_month 年/月 *
year int year_month 年/月 *
month day month_day 月/日 *
month int month_day 月/日 *
int day month_day 月/日 *
day month month_day 日/月
day int month_day 日/月
year_month day year_month_day 年+月/日 *
year_month int year_month_day 年+月/日 *
year month_day year_month_day 年/月+日 *
int month_day year_month_day 年/月+日 *
month_day year year_month_day 月+日/年
month_day int year_month_day 月+日/年

最終日指定:*3

左辺 右辺 結果 表記
month last_spec month_day_last 月/最終日 *
int last_spec month_day_last 月/最終日 *
last_spec month month_day_last 最終日/月
last_spec int month_day_last 最終日/月
year month_day_last year_month_day_last 年/月+最終日 *
int month_day_last year_month_day_last 年/月+最終日 *
month_day_last year year_month_day_last 月+最終日/年
month_day_last int year_month_day_last 月+最終日/年

第n曜日指定:

左辺 右辺 結果 表記
month weekday_indexed month_weekday 月/第n曜日 *
int weekday_indexed month_weekday 月/第n曜日 *
weekday_indexed month month_weekday 第n曜日/月
weekday_indexed int month_weekday 第n曜日/月
year_month weekday_indexed year_month_weekday 年+月/第n曜日 *
year month_weekday year_month_weekday 年/月+第n曜日 *
int month_weekday year_month_weekday 年/月+第n曜日 *
month_weekday year year_month_weekday 月+第n曜日/年
month_weekday int year_month_weekday 月+第n曜日/年

最終曜日指定:*4

左辺 右辺 結果 表記
month weekday_last month_weekday_last 月/最終曜日 *
int weekday_last month_weekday_last 月/最終曜日 *
weekday_last month month_weekday_last 最終曜日/月
weekday_last int month_weekday_last 最終曜日/月
year_month weekday_last year_month_weekday_last 年+月/最終曜日 *
year month_weekday_last year_month_weekday_last 年/月+最終曜日 *
int month_weekday_last year_month_weekday_last 年/月+最終曜日 *
month_weekday_last year year_month_weekday_last 月+最終曜日/年
month_weekday_last int year_month_weekday_last 月+最終曜日/年

関連URL

*1:date3 は std::chrome::month クラスの未規定(unspecified)仕様により、大半のC++処理系で month_day{month{81}, day{13}} として保持される可能性が高い。(C++20 27.8.4.1/p1, 27.8.4.2/p1)

*2:ISO 8601準拠の記述順(年-月-日)のみをサポートするならば、表中 * 印オーバーロードのみで十分だったはず。

*3:ある月の最終日表現には std::chrono::last 定数を用いる。

*4:ある月の最終曜日を std::chrono::weekday_last 型 = std::chrono::weekday 型 + std::chrono::last 定数で表現する。

*5:C++20のベースとなったCalendarライブラリに関するCppCon 2015プレゼンテーション

{void,value,both}-compatibleラムダ式

プログラミング言語Javaにおけるラムダ式は、その本体部に応じてvoid-compatible/value-compatible/その両方に区分される。

void-compatibleラムダ式Runnableなどの戻り値を持たない(void)関数型インタフェース(functional interface)へ、value-compatibleラムダ式IntSupplierなどの戻り値をもつ関数型インタフェースへと代入できる。

import java.util.function.IntSupplier;

// void-compatible
Runnable    r1 = () -> { };  // OK
IntSupplier s1 = () -> { };  // NG

// value-compatible
Runnable    r2 = () -> { return 42; };  // NG
IntSupplier s2 = () -> { return 42; };  // OK

// void-compatible かつ value-compatible
Runnable    r3 = () -> { throw new NullPointerException(); };  // OK
IntSupplier s3 = () -> { throw new NullPointerException(); };  // OK
final boolean TRUE = true;
Runnable    r4 = () -> { while (TRUE); };  // OK
IntSupplier s4 = () -> { while (TRUE); };  // OK

Java Language Specification, Java SE 8 Editionより一部引用。

The rules in this section define two technical terms:

  • whether a statement is reachable
  • whether a statement can complete normally

(snip)

The rules are as follows:

  • (snip)
  • A while statement can complete normally iff at least one of the following is true:
    • The while statement is reachable and the condition expression is not a constant expression (§15.28) with value true.
    • There is a reachable break statement that exits the while statement.
  • (snip)
  • A break, continue, return, or throw statement cannot complete normally.
  • (snip)
Chapter 14. Blocks and Statements, 14.21. Unreachable Statements

A block lambda body is void-compatible if every return statement in the block has the form return;.

A block lambda body is value-compatible if it cannot complete normally (§14.21) and every return statement in the block has the form return Expression;.

(snip)

Note that the void/value-compatible definition is not a strictly structural property: "can complete normally" depends on the values of constant expressions, and these may include names that reference constant variables.

Chapter 15. Expressions, 15.27.2. Lambda Body

関連URL

returns_twice属性

GCCとClangの独自拡張 returns_twice 属性についてメモ。

対象関数から「2回以上制御が戻ってくる可能性」*1コンパイラに伝える属性。コンパイラによる一部の最適化処理を抑止する。

The returns_twice attribute tells the compiler that a function may return more than one time. The compiler ensures that all registers are dead before calling such a function and emits a warning about the variables that may be clobbered after the second return from the function. Examples of such functions are setjmp and vfork. The longjmp-like counterpart of such function, if any, might need to be marked with the noreturn attribute.

https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html

returns_twice
This attribute indicates that this function can return twice. The C setjmp is an example of such a function. The compiler disables some optimizations (like tail calls) in the caller of these functions.

https://llvm.org/docs/LangRef.html

関連URL

*1:関数から制御が戻ってこないことを表明するnoreturn属性と対をなす。

-fimplicit-constexprオプション

gcc(g++) 12.1にて、inline関数に対して暗黙に constexpr 指定を行うコンパイルオプション -fimplicit-constexpr が導入された。同オプションはC++14/17/20言語仕様に対する独自拡張として機能する。

With each successive C++ standard the restrictions on the use of the constexpr keyword for functions get weaker and weaker; it recently occurred to me that it is heading toward the same fate as the C register keyword, which was once useful for optimization but became obsolete. Similarly, it seems to me that we should be able to just treat inlines as constexpr functions and not make people add the extra keyword everywhere.

https://gcc.gnu.org/git/?p=gcc.git;a=commitdiff;h=87c2080b

関連URL

連番配列を生成

JavaScript(ECMAScript)で連番配列を生成するコード片。いわゆる range 関数に相当。

// NG: [undefined, undefined, undefined, undefined, undefined]
// { length: 5 } のみ設定されたArrayオブジェクトが生成される
// 各要素は未設定(undefinedとも異なる状態)となっている
Array(5).map((val, idx) => idx);
[,,,,,].map((val, idx) => idx);

// OK: [0, 1, 2, 3, 4]
Array(5).fill(undefined).map((val, idx) => idx);
Array(5).fill(0).map((val, idx) => idx);
Array.from(Array(5), (val, idx) => idx);
Array.from({length: 5}, (val, idx) => idx);
[...Array(5)].map((val, idx) => idx);
[...Array(5).keys()];

関連URL