18.関数
18.1 <cstdarg> スタイル可変引数リスト
もし、
parameter-declaration-clause(パラメータ宣言節)が省略で終わっており、省略に対応した引数のいずれかが nullptr で呼び出されていた場合、そのプログラムは不正です。
[注意:nullptr の型は言語において直接的に表現可能ではありません。
<cstdarg> 機構は表現可能型を要求しているため、変数引数リストを通り抜けてから引数を取り出すことが可能です。][例:
void f(const char* pc, ...) {}
int main() {
f(nullptr); // 正当
f("abc", nullptr); // 不正
f("abc", 10, nullptr); // 不正
}
]
18.2 名前検索
メタデータの詳細は
§34.6.1 を参照のこと。
18.3 多重定義解決
文字列リテラル変換、ボックス化変換、ブーリアン、そしてハンドル変換を適応させるために、標準C++ §13.3.3.1.1「標準変換順序」のテーブル9「変換」は以下のグレーで示されるいくつかの新しい行を持つことになります。
| 変換 | カテゴリ | ランク | 項 |
| 変換不要 | 一意性 | 厳密一致 | |
| 文字列リテラル変換 | 一意性 | 厳密一致 | |
| 左辺値・右辺値変換 | 左辺値変形 | 厳密一致 | 4.1 |
| 配列・ポインタ変換 | 左辺値変形 | 厳密一致 | 4.2 |
| 関数・ポインタ変換 | 左辺値変形 | 厳密一致 | 4.3 |
| 修飾名変換 | 修飾一致 | 厳密一致 | 4.4 |
| ブーリアン等価性 | 修飾一致 | 厳密一致 | |
| 整数昇格 | 昇格 | 昇格 | 4.5 |
| 浮動小数点昇格 | 昇格 | 昇格 | 4.6 |
| ボックス化変換 | 昇格 | 昇格 | |
| 整数変換 | 変換 | 変換 | 4.7 |
| 浮動小数変換 | 変換 | 変換 | 4.8 |
| 浮動小数・整数変換 | 変換 | 変換 | 4.9 |
| ポインタ変換 | 変換 | 変換 | 4.10 |
| ポインタ・メンバ変換 | 変換 | 変換 | 4.11 |
| ハンドル変換 | 変換 | 変換 | |
| ブーリアン変換 | 変換 | 変換 | 4.12 |
18.4 パラメータ配列
標準C++ は可変長引数リストをメンバ関数、非メンバ関数の両方についてサポートしています。
しかしながら、その使われているアプローチはタイプ・セーフ(型安全)ではありません。
C++/CLI は
parameter-arrays(パラメータ配列)を使うタイプ・セーフな方法を追加します。
パラメータ配列は以下のように定義されます。
parameter-array:
attributesopt ... parameter-declaration
parameter-array(パラメータ配列)は
attributes(属性)(
§29 )のオプション・セットと省略記号、そして、
parameter-declaration(パラメータ宣言)でできています。
パラメータ配列は与えられた名前で与えられた CLI 配列型の単一パラメータを宣言します。
パラメータ配列の CLI 配列型は一次元 CLI 配列型(
§24.1 )であるべきです。
関数呼び出しにおいて、パラメータ配列を特定の与えられた CLI 配列型のひとつだけの引数であることも、0以上の引数の指定された CLI 配列要素であることも、共に許します。
もし、
parameter-declaration(パラメータ宣言)がデフォルト引数を含んでいた場合、そのプログラムは不正です。[例:
void f(... array<Object^>^ p);
int main() {
f();
f(nullptr);
f(1, 2);
f(nullptr, nullptr);
f(gcnew array<Object^>(1));
f(gcnew array<Object^>(1), gcnew array<Object^>(2));
}
]
[例:
void F1(... array<String^>^ list) {
for ( int i=0; i < list->Length; i++ )
Console::Write("{0} ", list[i]);
Console::WriteLine();
}
void F2(... array<Object^>^ list) {
for each (Object^ element in list)
Console::Write("{0} ", element);
Console::WriteLine();
}
int main() {
F1("1", "2", "3");
F2(1, L'a', "test");
array<String^>^ myarray
= gcnew array<String^> { "a", "b", "c" };
F1(myarray);
}
出力結果は以下の通りです。
1 2 3
1 a test
a b c
パラメータ配列を持つ関数が呼び出された時、呼び出しはパラメータ配列に対応する引数のリストを挿入した
array-init(配列初期化)(
§24.6 )を伴った
new-expression(new演算)(
§15.4.6 )であるかのように進みます。
パラメータ配列に0個の引数が与えられた場合、0の長さのCLI配列が渡されます。
[例:与えられた宣言
void F(int x, int y, ... array<Object^>^args);
以下の関数呼び出しは
F(10, 20);
F(10, 20, 30, 40);
F(10, 20, 1, "hello", 3.0);
次に厳密に対応します。
F(10, 20, nullptr);
F(10, 20, gcnew array<System::Object^> { 30, 40 });
F(10, 20, gcnew array<System::Object^> { 1, "hello", 3.0 });
]
パラメータ配列は対応する型の非パラメータ CLI 配列の引数を取る関数に引き渡すことができます。[例:
void f(array<int>^ pArray); // パラメータ配列ではない
void g(double value, ... array<int>^ p) {
f(p); // ok
}
]
array 型の引数は、パラメータ配列変換シーケンスを呼び出すこと無しに、パラメータ配列パラメータを持つ関数に引き渡されます。
[注意:ハンドル変換で発生するように、パラメータ配列変換無しにパラメータ配列の型に変換することができる array 型引数はパラメータ配列変換順序の用意をしないでしょう。]
パラメータ配列を持つ関数が多重定義解決の候補セットに含まれているとき、二つの関数シグネイチャが含まれます。関数シグネイチャ T
R F(T
1, T
2, …, ... array<T
P>) が与えられると、
exact form (厳密形式) は通常配列パラメータ (T
R, F(T
1, T
2, …, array<T
P>) でパラメータ配列パラメータを置き換え、
expanded form(拡張形式) は配列の要素型のパラメータ級数 (T
R F(T
1, T
2, …, T
P1, T
P2, …, T
PN) ) でパラメータ配列パラメータを置き換えます。拡張形式中のパラメータの数は関数呼び出しの引数の数に一致します。双方のシグネイチャ共に実行可能な関数の除去前に含まれます。もし、拡張形式が多重定義解決に選択された場合、パラメータ配列変換シーケンスが関数呼び出しに使われます。
メタデータは
§34.6.1 を参照のこと。
18.5 ネイティブ関数の取り込み
あるアセンブリ中でネイティブ・コードで定義された関数は他のアセンブリから(名前空間 System::Runtime::InteropServices からの)DllImportAttribute を使うことで関数宣言のグローバル・スコープ宣言か ref 型ないし、値クラスの静的メンバ関数として呼び出すことができます。
そのような関数宣言はまた定義であるべきではありません。この属性はインスタンス・メンバ関数を提供するべきではありません。
この属性はネイティブ・コード・アセンブリの名前と、そのアセンブリ中の関数名と、そのネイティブ・コード関数を使うために呼び出される変換と、文字列マーシャリングのための文字コードセットを提供します。[例:
// MyCLib.h
using namespace System::Runtime::InteropServices;
[DllImport("MyCLib.dll", CallingConvention =
CallingConvention::StdCall, EntryPoint = "Hypot")]
extern "C" double Hypotenuse(double s1, double s2);
// MyCLibApp.cpp
#include "MyCLib.h"
int main() {
Console::WriteLine("Hypotenuse = {0}", Hypotenuse(3, 4));
}
この場合では、Hypod という名前の関数は共有ライブラリ MyCLib.dll 中にあります。この名前は属性が与えられたプログラム要素のそれに、Hypotenuse という名付けられて、マップされます。
呼び出し規則は適切に指定されています。
Hypot 関数が記述された方法は、実装定義です。ここにある実装で記述されたバージョンがあります:
// MyCLib.c
#include <math.h>
__declspec(dllexport) double __stdcall Hypot(double side1, double side2)
{
return sqrt( (side1 * side1) + (side2 * side2) );
}
次の例では標準C ライブラリ関数 strcmp が取り込まれ、String^ から char* 変換が MarshalAssAttribute 属性(名前空間 System::Runtime::InteropServices より)の特性によって引数に対して発生します。
using namespace System::Runtime::InteropServices;
[DllImport("msvcrt.dll", CallingConvention = CallingConvention::Cdecl)]
extern "C" int strcmp(
[MarshalAs(UnmanagedType::LPStr)] System::String^ s1,
[MarshalAs(UnmanagedType::LPStr)] System::String^ s2 );
int main() {
String^ str1 = "red";
String^ str2 = "RED";
Console::WriteLine("Compare: {0}", strcmp(str1, str2));
}
]
メタデータは
§34.6.3 を参照のこと。
18.6 非メンバ関数
[注意:非メンバ関数は CLI によってある無名クラスのメンバとして扱われます。
しかしながら、C++/CLI のソースコードでは、そのような関数はそのクラス名で厳密に修飾することはできません。]
メタデータは
§34.6.4 を参照のこと。
18.7 属性
simple-declaration (単純宣言)か
member-declaration (メンバ宣言)の第一生成物のどちらかの結果となる
function-definition (関数定義)(
§19.4 )と関数宣言は属性を持つことができます。
単純宣言の結果は関数宣言とグローバル変数に属性を許すために次のように拡張されます。
simple-declaration:
attributesopt decl-specifier-seqopt init-declarator-listopt ;