yohhoyの日記

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

メタ関数と変数テンプレートの非互換性

C++言語における定数値を返すメタ関数X<T>::valueとその変数テンプレート版X_v<T>の微妙な非互換性について。

下記コードにおいて、[B]からインスタンス化される変数テンプレート定義の初期化子(initializer)は “immediate context” ではないため、パラメータ置換後の不完全型tuple_size<int>におけるvalueメンバ不在がコンパイルエラー(ill-formed)を引き起こす。[A] requires節の原子制約(atomic constraint)に置換した結果、その “immediate context” で不正な型や式が出現した場合、エラーとならずに制約が満たされない(not satisfied)と判断される。

#include <utility>

// tuple_size<T>メタ関数
template<class T>
constexpr int f() { return 1; }

template<class T>
  requires (std::tuple_size<T>::value == 2) // [A]★
constexpr int f() { return 2; }

static_assert( f<int>() == 1 );  // OK
static_assert( f<std::pair<int,int>>() == 2 ); // OK


// tuple_size_v<T>変数テンプレート
template<class T>
constexpr int g() { return 1; }

template<class T>
  requires (std::tuple_size_v<T> == 2) // [B]★
constexpr int g() { return 2; }

static_assert( g<int>() == 1 );  // NG: ill-formed
static_assert( g<std::pair<int,int>>() == 2 ); // OK

C++20/23

C++23現在は用語 “immediate context” が定義されないまま利用されており、長らく(2014年から!) CWG 1844 で問題提起されていた。C++20 13.5.1.2/p3, 13.10.2/p8, 20.5.2, 20.5.6/p4-5*1より一部引用(下線部は強調)。

3 To determine if an atomic constraint is satisfied, the parameter mapping and template arguments are first substituted into its expression. If substitution results in an invalid type or expression, the constraint is not satisfied. Otherwise, the lvalue-to-rvalue conversion (7.3.1) is performed if necessary, and E shall be a constant expression of type bool. The constraint is satisfied if and only if evaluation of E results in true. (snip)

8 If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written using the substituted arguments. [Note: If no diagnostic is required, the program is still ill-formed. Access checking is done as part of the substitution process. -- end note] Only invalid types and expressions in the immediate context of the function type, its template parameter types, and its explicit-specifier can result in a deduction failure. [Note: The substitution into types and expressions can result in effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-defined functions, etc. Such effects are not in the "immediate context" and can result in the program being ill-formed. -- end note]

namespace std {
  // ...
  template<class T> struct tuple_size;  // not defined
  template<class T> struct tuple_size<const T>;
  // ...
  template<class T>
  inline constexpr size_t tuple_size_v = tuple_size<T>::value;
}
template<class T> struct tuple_size<const T>;

4 Let TS denote tuple_size<T> of the cv-unqualified type T. If the expression TS::value is well-formed when treated as an unevaluated operand, then each specialization of the template meets the Cpp17UnaryTypeTrait requirements (20.15.1) with a base characteristic of
  integral_constant<size_t, TS::value>
Otherwise, it has no member value.
5 Access checking is performed as if in a context unrelated to TS and T. Only the validity of the immediate context of the expression is considered. [Note: The compilation of the expression 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]

C++26

C++2c(C++26)では提案文書P4149R1が採択され、用語 “immediate context” が明確に定義される。N5050 13.5.2.3/p3, 13.10.3.1/p7-8より一部引用(下線部は強調)。*2

3 To determine if an atomic constraint is satisfied, the parameter mapping and template arguments are first substituted into its expression. If substitution results in an invalid type or expression in the immediate context of the atomic constraint (13.10.3.1), the constraint is not satisfied. Otherwise, the lvalue-to-rvalue conversion (7.3.2) is performed if necessary, and E shall be a constant expression of type bool. The constraint is satisfied if and only if evaluation of E results in true. (snip)

7 The deduction substitution loci are

  • the function type outside of the noexcept-specifier,
  • the explicit-specifier,
  • the template parameter declarations, and
  • the template argument list of a partial specialization (13.7.6.1).

The substitution occurs in all types and expressions that are used in the deduction substitution loci. The expressions include not only constant expressions such as those that appear in array bounds or as constant template arguments but also general expressions (i.e., non-constant expressions) inside sizeof, decltype, and other contexts that allow non-constant expressions. The substitution proceeds in lexical order and stops when a condition that causes deduction to fail is encountered. If substitution into different declarations of the same function template would cause template instantiations to occur in a different order or not at all, the program is ill-formed; no diagnostic required.
(snip)
8 If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written in the same context using the substituted arguments.
[Note 5: If no diagnostic is required, the program is still ill-formed. Access checking is done as part of the substitution process. -- end note]
Invalid types and expressions can result in a deduction failure only in the immediate context of the deduction substitution loci. The immediate context of a deduction substitution locus is that deduction substitution locus, excluding bodies of lambda-expressions and separately instantiated constructs.
(snip)
[Note 6: Separately instantiated constructs are excluded from the immediate contexts even if they are instantiated at the same time. -- end note]
[Note 7: The substitution into types and expressions can result in effects such as the instantiation of template specializations, the generation of implicitly-defined functions, etc. Such effects are not in the immediate context and can result in the program being ill-formed. -- end note]

関連URL

*1:C++17にて LWG 2770 適用済み。

*2:loci は locus の 複数形。場所・位置の意。

#fatalディレクティブ for C2y

プログラミング言語Cの次期仕様C2yに向けて、ビルドプロセス*1を即時停止させるプリプロセッサディレクティブ#fatalの提案(PDF)N3960が提出されている。

ソースコードの重要な前提条件に違反するケースで(例:未対応のアーキテクチャなど)、後続ソースコードからビルドエラーが大量出力されるのを抑止する。ま、あっても良いんじゃない?

提案文書より例示コードを改変引用。★以後でMatrix型の未定義による副次的なエラーメッセージ出力を抑止できる。

// header.h
#ifdef TARGET_ARCH_SUPPORTED
typedef struct { double data[16]; int flags; } Matrix;
#else
#fatal "Unsupported target architecture!"  // ★
#endif

// main.c
#include "header.h"
Matrix g_m;

void setup(Matrix *m) {
  m->flags = 1;
  m->data[0] = 1.0;
}
// ...

おまけ:C23現在、プリプロセス時にユーザ定義メッセージを出力する診断ディレクティブ(diagnostic directives)#errorと#warningが定義される。C23 6/p4, 6.10.7/p1より引用。

4 The implementation shall not successfully translate a preprocessing translation unit containing a #error preprocessing directive unless it is part of a group skipped by conditional inclusion.

1 A preprocessing directive of either form
  # error pp-tokensopt new-line
  # warning pp-tokensopt new-line
causes the implementation to produce a diagnostic message that includes the specified sequence of preprocessing tokens.

関連URL

*1:C標準規格の用語では「翻訳フェーズ(translation phase)」と呼ばれる。一般にはプリプロセス+コンパイル+リンクから構成される。

ビットマスク型アノテーション

次期標準C++2d(C++29)に向けて、ビットマスク列挙型を実装するアノテーション(annotation)が提案されている。*1

列挙型定義に[[=std::bitmask_type]]アノテーションを指定するだけで、ビット操作|(OR), &(AND), ^(XOR), ~(NOT)演算子オーバーロードが自動的に実装される。提案文書P4313R1より例示コードを引用。

#include <bitmask>  // P4313R1
#include <iostream>

enum class [[=std::bitmask_type]] Permission {
  None    = 0,
  Read    = 1 << 0,
  Write   = 1 << 1,
  Execute = 1 << 2,
};

int main () {
  // Combine permissions
  Permission user_perms = Permission::Read | Permission::Write | Permission::Execute;

  if ((user_perms & Permission::Read) == Permission::Read)
    std::cout << "Can read";

  Permission can_read_and_write = Permission::Read | Permission::Write;
  if ((user_perms & can_read_and_write) == can_read_and_write)
    std::cout << "Can read and write";
}

C++26 Reflection(→id:yohhoy:20250305)利用でお手軽に実装可能。参照実装より一部引用:

namespace std {
  inline constexpr struct BitmaskType {} bitmask_type;

  template <typename T>
  concept BitmaskTypeLike =
    std::is_enum_v<T> and 
    (!std::meta::annotations_of_with_type(^^T, std::meta::type_of(^^bitmask_type)).empty());
} // std

template <std::BitmaskTypeLike T>
constexpr T operator|(T lhs, T rhs) {
  return static_cast<T>(std::to_underlying(lhs) | std::to_underlying(rhs));
}
// ...

関連URL

*1:アノテーション(annotation)はC++26 Reflectionで導入される新しい言語要素。属性(attribute)と同様に二重角括弧 [[ ]] で囲い、アノテーション名の直前に等号 = を記述する。

識別子の有効文字数 in 標準C

プログラミング言語Cの言語仕様における識別子(Identifiers)の有効文字数(significant initial characters)の変遷についてメモ。

言語仕様によって識別子の同値比較で利用される「有効文字数の最小長」が規定される。別の表現では「有効文字数の最小長」を超える識別子名は、処理系によっては意図せず同値判定される可能性がある。(例:ANSI C準拠の処理系では外部識別子FUNCTIONとfunctiは同値判定されうる。)

プリプロセッサとコンパイラからのみ参照される内部識別子(internal identifier)と、リンカから参照される外部識別子(external identifier)に対して、言語バージョン毎に有効文字数の最小長が規定される。C90以前の外部識別子では大文字/小文字が区別される保証が無かった。

バージョン 内部識別子 外部識別子
K&R C 第1版 8文字 6文字 ※
K&R C 第2版 31文字 6文字 ※
ANSI C/ISO C90 31文字 6文字 ※
ISO C99以降 63文字 31文字

※印=アルファベット大文字/小文字が区別されない(case-insensitive)可能性あり

おまけ:C++言語仕様には類似の制約はなく、識別子全体が有効文字として扱われる。C++03 2.10 Identifiers: "Upper- and lower-case letters are different. All characters are significant."

K&R C 第1版

K&R C 1st Ed. 2.2 Identifiers (Names)より引用(下線部は強調)。

An identifier is a sequence of letters and digits; the first character must be a letter. The underscore _ counts as a letter. Upper and lower case letters are different. No more than the first eight characters are significant, although more may be used. External identifiers, which are used by various assemblers and loaders, are more restricted:

DEC PDP-11 7 characters, 2 cases
Honeywell 6000 6 characters, 1 case
IBM 360/370 7 characters, 1 case
Interdata 8/32 8 characters, 2 cases

K&R C 第2版

K&R C 2nd Ed. A.2.3 Identifiersより引用(下線部は強調)。

An identifier is a sequence of letters and digits. The first character must be a letter; the underscore _ counts as a letter. Upper and lower case letters are different. Identifiers may have any length, and for internal identifiers, at least the first 31 characters are significant; some implementations may take more characters significant. Internal identifiers include preprocessor macro names and all other names that do not have external linkage.
Identifiers with external linkage are more restricted: implementations may make as few as the first six characters significant, and may ignore case distinctions.

C89/C90

ANSI C(C89) 2.2.4.1 Translation Limits, 3.1.2 Identifiersより一部引用(下線部は強調)。

The implementation shall be able to translate and execute at least one program that contains at least one instance of every one of the following limits:

  • (snip)
  • 31 significant initial characters in an internal identifier or a macro name
  • 6 significant initial characters in an external identifier
  • (snip)

Implementation limits
The implementation shall treat at least the first 31 characters of an internal name (a macro name or an identifier that does not have external linkage) as significant. Corresponding lower-case and upper-case letters are different. The implementation may further restrict the significance of an external name (an identifier that has external linkage) to six characters and may ignore distinctions of alphabetical case for such names. These limitations on identifiers are all implementation-defined.
Any identifiers that differ in a significant character are different identifiers. If two identifiers differ in a non-significant character, the behavior is undefined.

C99以降

C99 5.2.4.1/p1, 6.4.2.1/p5-6より一部引用(下線部は強調)。C23 5.2.5.2/p1, 6.4.2.1/p13-14でも同一。

1 The implementation shall be able to translate and execute at least one program that contains at least one instance of every one of the following limits:

  • (snip)
  • 63 significant initial characters in an internal identifier or a macro name (each universal character name or extended source character is considered a single character)
  • 31 significant initial characters in an external identifier (each universal character name specifying a short identifier of 0000FFFF or less is considered 6 characters, each universal character name specifying a short identifier of 00010000 or more is considered 10 characters, and each extended source character is considered the same number of characters as the corresponding universal character name, if any)
  • (snip)

Implementation limits
5 As discussed in 5.2.4.1, an implementation may limit the number of significant initial characters in an identifier; the limit for an external name (an identifier that has external linkage) may be more restrictive than that for an internal name (a macro name or an identifier that does not have external linkage). The number of significant characters in an identifier is implementation-defined.
6 Any identifiers that differ in a significant character are different identifiers. If two identifiers differ only in nonsignificant characters, the behavior is undefined.

C99 Rationale 6.4.2.1より引用。

Because of the linkers available at the time, the C89 Committee made the decision to restrict significance of identifiers with external linkage to six case-insensitive characters. This limit is increased in C99 to 31 case-sensitive characters.

While an implementation is not obliged to remember more than the first 63 characters of an identifier with internal linkage, or the first 31 characters of an identifier with external linkage, the programmer is effectively prohibited from intentionally creating two different identifiers that are the same within the appropriate length. Implementations may therefore store the full identifier; they are not obliged to truncate to 63 or 31.

関連URL

std::function_refプライマリテンプレート宣言

C++2c(C++26)標準ライブラリstd::function_ref<R(ArgsTypes...)>クラステンプレートでは、先行宣言のみプライマリテンプレートが可変長引数テンプレート(variadic templates)となっている。技術的には単一のテンプレートパラメータで十分だが、将来拡張のために可変長テンプレートパラメータにしたとのこと。

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

// [func.wrap.ref], non-owning wrapper
template<class... S> class function_ref;  // freestanding, not defined
template<class R, class... ArgTypes>
  class function_ref<R(ArgTypes...) cv noexcept(noex)>;

提案文書P0792R14, Changelogより引用(下線部は強調)。

R9

  • Declare the main template as variadic for future extension;
  • Allow declaring a variable of function_ref constinit.
P0792R14 function_ref: a type-erased callable reference

同様に、std::move_only_function(C++23)とstd::copyable_function(C++2c)のプライマリテンプレート宣言も可変長テンプレートパラメータを持つ。*1
一方、std::function(C++11)のプライマリテンプレート宣言は単一のテンプレートパラメータを持つ。

// [func.wrap.func], class template function
template<class> class function;  // not defined
template<class R, class... ArgTypes>
  class function<R(ArgTypes...)>;

// [func.wrap.move], move-only wrapper
template<class... S> class move_only_function;  // not defined
template<class R, class... ArgTypes>
  class move_only_function<R(ArgTypes...) cv ref noexcept(noex)>;

// [func.wrap.copy], copyable wrapper
template<class... S> class copyable_function;  // not defined
template<class R, class... ArgTypes>
  class copyable_function<R(ArgTypes...) cv ref noexcept(noex)>;

ノート:std::functionクラステンプレートはC++2d以降で非推奨化(deprecated)される可能性があり*2、将来的な変更見込みはないだろう。

関連URL

*1:move_only_function 検討途中の提案文書 P0288R6 にて単一パラメータから可変長パラメータへと変更されている。https://github.com/cplusplus/papers/issues/400 でも詳細経緯は不明。

*2:https://github.com/cplusplus/papers/issues/1745

memccpy関数 in 標準C

C23標準ライブラリで追加されたmemccpy関数*1についてメモ。POSIX関数からの標準C入り。

コピー元s2からコピー先s1に対して、文字cの初回出現まで、もしくは最大n文字までコピーする関数。標準ヘッダ<string.h>にて宣言される。

void *memccpy(void * restrict s1, const void * restrict s2, int c, size_t n);

第3引数cにNUL文字('\0')を指定することで、有限長バッファへの安全な文字列コピーを実現できる。下記はOpenBSD strlcpy関数相当の実装例。*2

#include <string.h>

void my_strlcpy(char* dst, const char* src, size_t size)
{
  char *p = memccpy(dst, src, '\0', size);
  if ( !p ) {
    // コピー元範囲 [src, src+size) にNUL文字が存在しない場合、
    // コピー先末尾 dst[size-1] へのNUL代入で文字列を終端させる。
    dst[size-1] = '\0';
  }
}

const char *str = "Hello C";

char buf1[10];
my_strlcpy(buf1, str, sizeof(buf1);
// buf1 = "Hello C"

char buf2[5];
my_strlcpy(buf2, str, sizeof(buf2);
// buf2 = "Hell"

上記の文字列コピー処理は、古くからC標準ライブラリに存在するstrncpy関数(→id:yohhoy:20161102)よりも効率的に動作する。

void wasteful_strlcpy(char* dst, const char* src, size_t size)
{
  strncpy(dst, src, size);  // OK...だが非効率
  // strlen(src) < size-1 の場合、コピー先の残り領域にもNUL文字が設定される。
  // バッファサイズsizeが大きいほど無駄なメモリ書込処理が行われる。
  // またコピー先バッファ末尾へのNUL代入も必要となる。
  // strncpy関数の仕様上、戻り値からは代入の必要性を判定できない。
  dst[size-1] = '\0';
  // メモリ書込アクセスを嫌って分岐させるなら下記コードとなるが、
  // strncpy内部にてバッファ全域への書込アクセスが行われている...
  // if (dst[size-1] != '\0') { dst[size-1] = '\0'; }
}

C23 7.26.2.2/p2-3より引用。C++2c(C++26)はC23規格をNormative参照する。

Description
2 The memccpy function copies characters from the object pointed to by s2 into the object pointed to by s1, stopping after the first occurrence of character c (converted to an unsigned char) is copied, or after n characters are copied, whichever comes first. If copying takes place between objects that overlap, the behavior is undefined.
Returns
3 The memccpy function returns a pointer to the character after the copy of c in s1, or a null pointer if c was not found in the first n characters of s2.

関連URL

*1:ANSI C時代から存在する memcpy 関数とは別モノ。関数名 memccpy にcが2個含まれることに注意!

*2:OpenBSDのstrlcpy関数は実際にコピーされた文字列長を返す。

ゼロ幅ビットフィールド

プログラミング言語C/C++では、ビット幅が値0の無名(unnamed)ビットフィールドを宣言できる。ゼロ幅ビットフィールドにより、その前後ビットフィールドへの割当バイト(byte)を分離する。

struct S {
  int b1 : 4;
  int b2 : 2;
  int : 0;  // (無名)ゼロ幅ビットフィールド
  int b3 : 2;
};

上記例のビットフィールドb1, b2は同一バイト位置に割り当てられるが、ゼロ幅ビットフィールドを挟んだb3は前2つとは異なるバイト位置に割り当てられることが保証される。

C言語

C99 6.7.2.1/p3, 11より一部引用(下線部は強調)。

Syntax
(snip)
struct-declarator:
  declarator
  declaratoropt : constant-expression

3 The expression that specifies the width of a bit-field shall be an integer constant expression with a nonnegative value that does not exceed the width of an object of the type that would be specified were the colon and expression omitted. If the value is zero, the declaration shall have no declarator.

11 A bit-field declaration with no declarator, but only a colon and a width, indicates an unnamed bit-field. As a special case, a bit-field structure member with a width of 0 indicates that no further bit-field is to be packed into the unit in which the previous bit-field, if any, was placed.

C++言語

C++03 9.6/p1-2より引用(下線部は強調)。

1 A member-declarator of the form
  identifieropt : constant-expression
specifies a bit-field; its length is set off from the bit-field name by a colon. The bit-field attribute is not part of the type of the class member. The constant-expression shall be an integral constant-expression with a value greater than or equal to zero. The constant-expression may be larger than the number of bits in the object representation (3.9) of the bit-field’s type; in such cases the extra bits are used as padding bits and do not participate in the value representation (3.9) of the bit-field. Allocation of bit-fields within a class object is implementation-defined. Alignment of bit-fields is implementation-defined. Bit-fields are packed into some addressable allocation unit. [Note: bit-fields straddle allocation units on some machines and not on others. Bit-fields are assigned right-to-left on some machines, left-to-right on others. ]
2 A declaration for a bit-field that omits the identifier declares an unnamed bit-field. Unnamed bit-fields are not members and cannot be initialized. [Note: an unnamed bit-field is useful for padding to conform to externally-imposed layouts. ] As a special case, an unnamed bit-field with a width of zero specifies alignment of the next bit-field at an allocation unit boundary. Only when declaring an unnamed bit-field may the constant-expression be a value equal to zero.

関連URL