yohhoyの日記

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

NEO assertマクロ

プログラミング言語C/C++の次期標準規格C2x(C23)およびC++2c(C++26)では、アサーションマクロassertの改善が行われる。

#include <assert.h> // C/C++
#include <cassert>  // C++のみ

int is_valid(int);

assert( "42 shall be vaild", is_valid(42) );
// NG: C17/C++20現在
// OK: C2x/C++2c以降

assert(("42 shall be vaild", is_valid(42)));
// OK: 式全体を括弧で囲う

C言語ではトラブルを引き起こすケースは少ないが*1C++言語では初期化子リストやテンプレートパラメータなどでトラブル発生頻度が高い。はず。

// C++事例
assert( get_vec() == std::vector{1, 2, 3} );
assert( calc_mul<int,2>(3) == 6 );

本件は遡及適用されるバグ修正ではなく、新機能として取り扱われる模様。JTC1/SC22/WG14 2022年1-2月会議録*2より引用:

5.29 Sommerlad, Make assert() macro user friendly for C and C++ v2 [N 2829]
Discussion of how the current wording says "scalar expression" so this could be seen as a bug fix.

Concerns about making this a special macro definition vs all the other macros in the standard. Makes it inconsistent. Counters included incremental improvement or that this was the only macro that is a problem (author mentioned specifically for C++).

関連URL

*1:本文中にあるカンマ演算子を用いた作為的な例も、実用的には論理積演算子(&&)を用いて代替記述できる。

*2:https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2991.pdf

signal関数プロトタイプ宣言

C++17以降のC++標準ライブラリ仕様では、signal関数のプロトタイプ宣言が読みやすく書き直されている。

C++17仕様

C++標準ライブラリ仕様としてプロトタイプ宣言が行われている。C++17 21.10.3より宣言を引用:

Header <csignal> synopsis

namespace std {
  // 21.10.4, signal handlers
  extern "C" using signal-handler = void(int); // exposition only
  signal-handler* signal(int sig, signal-handler* func);
}

The contents of the header <csignal> are the same as the C standard library header <signal.h>.

またC++標準において、シグナルハンドラ内で行っても良い Signal-safe 操作の定義がなされている。C++17 21.10.4/p3より引用:

An evaluation is signal-safe unless it includes one of the following:

  • a call to any standard library function, except for plain lock-free atomic operations and functions explicitly identified as signal-safe. [Note: This implicitly excludes the use of new and delete expressions that rely on a library-provided memory allocator. -- end note]
  • an access to an object with thread storage duration;
  • a dynamic_cast expression;
  • throwing of an exception;
  • control entering a try-block or function-try-block;
  • initialization of a variable with static storage duration requiring dynamic initialization (6.6.3, 9.7); or
  • waiting for the completion of the initialization of a variable with static storage duration (9.7).

A signal handler invocation has undefined behavior if it includes an evaluation that is not signal-safe.

C++03/11/14仕様

C++14以前の仕様では独自のプロトタイプ宣言を行わず、標準Cヘッダ<signal.h>を参照している。

シグナルハンドラ内からの(一部例外を除く)C++関数呼び出しは処理系定義(implementation-defined)とされ、Signal-safeに関する言及は存在しない。C++14 18.10/p10より一部引用:

The common subset of the C and C++ languages consists of all declarations, definitions, and expressions that may appear in a well formed C++ program and also in a conforming C program. A POF ("plain old function") is a function that uses only features from this common subset, and that does not directly or indirectly use any function that is not a POF, except that it may use plain lock-free atomic operations. (snip) All signal handlers shall have C linkage. The behavior of any function other than a POF used as a signal handler in a C++ program is implementation-defined.228
脚注228) In particular, a signal handler using exception handling is very likely to have problems. Also, invoking std::exit may cause destruction of objects, including those of the standard library implementation, which, in general, yields undefined behavior in a signal handler (see 1.9).

C仕様

C17 7.14.1.1/p1より宣言を引用:

Synopsis

#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);

シグナルハンドラ内で許可される操作は、条件付きで明示的に列挙されている。C17 7.14.1.1/p4-5より一部引用:

4 If the signal occurs as the result of calling the abort or raise function, the signal handler shall not call the raise function.
5 If the signal occurs other than as the result of calling the abort or raise function, the behavior is undefined if the signal handler refers to any object with static or thread storage duration that is not a lock-free atomic object other than by assigning a value to an object declared as volatile sig_atomic_t, or the signal handler calls any function in the standard library other than (snip)

関連URL

タグ型の実装イディオム

C++標準ライブラリで使われるタグ型(tag type)とタグ値の実装イディオム。

デフォルトコンストラクタへのexplicit指定は、{}によるタグ型(mytag_t)デフォルト構築を禁止するため。

struct mytag_t {
  explicit mytag_t() = default;
};

inline constexpr mytag_t mytag{};  // C++17以降
struct S {};
void f(S);  // #1
void f(mytag_t);  // #2

f({});  // #1を呼び出す

関連URL

共有ライブラリファイル on macOS

macOS Big Sur 11.0.1以降では、システム提供される共有ライブラリファイル(dylib)はファイルシステム上に実体が存在しない。

macOS Big Sur 11.0.1 リリースノートより引用(下線部は強調)。

New in macOS Big Sur 11.0.1, the system ships with a built-in dynamic linker cache of all system-provided libraries. As part of this change, copies of dynamic libraries are no longer present on the filesystem. Code that attempts to check for dynamic library presence by looking for a file at a path or enumerating a directory will fail. Instead, check for library presence by attempting to dlopen() the path, which will correctly check for the library in the cache. (62986286)

macOS Big Sur 11.0.1 Release Notes | Apple Developer Documentation

macOS Ventura 13.4.1での実行結果:

$ otool -L /usr/bin/otool
/usr/bin/otool:
    /usr/lib/libxcselect.dylib (compatibility version 1.0.0, current version 1.0.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1319.100.3)
$ file /usr/lib/libSystem.B.dylib
/usr/lib/libSystem.B.dylib: cannot open `/usr/lib/libSystem.B.dylib' (No such file or directory)

関連URL

式のコンパイル時評価判定

C++20言語機能を利用した、ある式がコンパイル時に評価可能かを判定するメタ関数的なもの。id:yohhoy:20190528 の別解。

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

// C++20以降
template<class Lambda, int = (Lambda{}(), 0)>
constexpr bool is_constexpr(Lambda) { return true; }
constexpr bool is_constexpr(...) { return false; }

template<int N> void do_stuff();
void do_stuff(int N);

template<typename T>
int process(const T&)
{
  // 式 T::number() をコンパイル時に評価可能?
  if constexpr (is_constexpr([]{ T::number(); })) {
    do_stuff< T::number() >();
    return 1;
  } else {
    do_stuff( T::number() );
    return 2;
  }
}

struct X { static constexpr int number() { return 42; } };
struct Y { static int number() { return 42; } };
int main()
{
  assert(process(X{}) == 1);  // OK(C++17: NG)
  assert(process(Y{}) == 2);  // OK
}

関連URL

関数名/メンバ関数名とアドレス演算子

プログラミング言語C++において、通常関数名やstaticメンバ関数名から関数ポインタ型へは暗黙変換が行われるが、メンバ関数名からメンバ関数ポインタ型への変換はアドレス演算子&利用が必須。

void f0();
struct S {
  static void f1();
  void mf();
};

// (通常)関数/staticメンバ関数
using PF = void (*)();
PF p0a = &f0;  // OK
PF p0b =  f0;  // OK: 暗黙変換
PF p1a = &S::f1;  // OK
PF p1b =  S::f1;  // OK: 暗黙変換

// メンバ関数
using PMF = void (S::*)();
PMF pmfa = &S::mf;  // OK
PMF pmfb =  S::mf;  // NG: ill-formed

C++03 4.3/p1, 5.1/p10より一部引用。C++20現在は7.3.4/p1, 7.5.4.1/p2が対応。

An lvalue of function type T can be converted to an rvalue of type "pointer to T." The result is a pointer to the function.50)

脚注50) This conversion never applies to nonstatic member functions because an lvalue that refers to a nonstatic member function cannot be obtained.

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

  • (snip)
  • to form a pointer to member (5.3.1), or
  • (snip)

関連URL

std::destructibleコンセプト

C++20標準ライブラリstd::destructible<T>コンセプトに関するメモ。

ある型Tが「例外送出なしにデストラクト可能」と制約(constraint)するコンセプト。デストラクタは規定で暗黙のnothrow指定が行われるため*1、明示的にnothrow(false)指定を行わない限りあらゆるオブジェクト型が満たすコンセプトとなる。*2

std::destructibleコンセプトは、標準ヘッダ <concepts> 提供のオブジェクト関連コンセプトにより直接/間接的に包摂(subsume)される。

  • std::constructible_from
  • std::default_initializable
  • std::move_constructible
  • std::copy_constructible
  • std::movable
  • std::copyable
  • std::semiregular
  • std::regular

C++20 18.4.10, 18.4.11より引用(下線部は強調)。

1 The destructible concept specifies properties of all types, instances of which can be destroyed at the end of their lifetime, or reference types.

template<class T>
  concept destructible = is_nothrow_destructible_v<T>;

2 [Note: Unlike the Cpp17Destructible requirements (Table 32), this concept forbids destructors that are potentially throwing, even if a particular invocation of the destructor does not actually throw. -- end note]

1 The constructible_from concept constrains the initialization of a variable of a given type with a particular set of argument types.

template<class T, class... Args>
  concept constructible_from = destructible<T> && is_constructible_v<T, Args...>;

2016年頃のC++ Ranges拡張PDTS*3時点でも、Destructibleコンセプト*4はオブジェクトコンセプトにおける最も基本的なコンセプトと説明されていた。(PDF)N4622より一部引用(下線部は強調)。

19.4 Object concepts
1 This section describes concepts that specify the basis of the value-oriented programming style on which the library is based.

19.4.1 Concept Destructible
1 The Destructible concept is the base of the hierarchy of object concepts. It specifies properties that all such object types have in common.

template <class T>
concept bool Destructible() {
  return requires (T t, const T ct, T* p) {
    { t.~T() } noexcept;
    /*(snip)*/
  };
}

(snip)

19.4.2 Concept Constructible
1 The Constructible concept is used to constrain the type of a variable to be either an object type constructible from a given set of argument types, or a reference type that can be bound to those arguments.

template <class T, class ...Args>
concept bool __ConstructibleObject =
  Destructible<T>() && requires (Args&& ...args) { /*(snip)*/ };

template <class T, class ...Args>
concept bool Constructible() {
  return __ConstructibleObject<T, Args...> || /*(snip)*/
}

関連URL

*1:C++20 14.5/p8: "The exception specification for an implicitly-declared destructor, or a destructor without a noexcept-specifier, is potentially-throwing if and only if any of the destructors for any of its potentially constructed subobjects is potentially-throwing or the destructor is virtual and the destructor of any virtual base class is potentially-throwing."

*2:参照型(reference type)はオブジェクト型(object type)ではないが(C++20 6.8.1/p8)、std::is_nothrow_destructible メタ関数の定義より std::destructible コンセプトを満たす。(cv修飾された)void 型/関数型/要素数未定の配列型は、std::destructible コンセプトを満たさない。

*3:Preliminary Draft Technical Specification

*4:検討段階におけるコンセプト関連の言語仕様は、C++20現在のコンセプト仕様とは異なっている。またコンセプト命名規則はその後の(PDF)P1754R1採択により Destructible→destructible/Constructible→constructible_from へと変更されている。