Visual Studioでstd::min()/std::max()がエラーになるときの回避策

C++11からstd::minとstd::maxは複数の引数を受け取ることができるようになりました。

#include <algorithm>

int i = std::min({1, 2, 3, 4});

ところが、Visual Studioで上記のコードを実行するとコンパイルエラーになります。

原因は、windows.hにmin/maxのマクロが定義されているため。

マイクロソフトのページにある解決策は、「NOMINMAXのプリプロセッサシンボルを定義します。」とあります。
しかし、それができない状況もあります。

他の回避策が「windows.hのmin/maxマクロ回避策4パターン – yohhoyの日記」に紹介されていました。

(std::min)(…)や(std::max)(…)のように、括弧で囲みマクロ展開を抑止することで問題を回避できます。

#include <algorithm>

int i = (std::min)({1, 2, 3, 4});

これでエラーにならなくなりました。

C++で16進数・10進数・8進数を変換するには

数値を16進数の文字列に変換する

    std::stringstream ss;
    ss << std::hex << 16777215;
    std::cout << ss.str() << std::endl; //=> ffffff

数値を8進数の文字列に変換する

    std::stringstream ss;
    ss << std::oct << 16777215;
    std::cout << ss.str() << std::endl; //=> 77777777

16進数の文字列を数値に変換する

    int n;
    std::istringstream("ffffff") >> std::hex >> n;
    std::cout << n << std::endl; //=> 16777215

8進数の文字列を数値に変換する

    int n;
    std::istringstream("77777777") >> std::oct >> n;
    std::cout << n << std::endl; //=> 16777215

C++11で数値と文字列の変換

C++11から数値と文字列の変換を簡単にできるようになりました。

数値を文字列に変換する

数値を文字列に変換するには「std::to_string」関数を使用します。

例:std::stringに変換する

int val = 123;
std::string str = std::to_string(val); // "123"

例:std::wstringに変換する

int val = 123;
std::wstring str = std::to_wstring(val); // "123"

文字列をint型に変換する

文字列をint型に変換するには「std::stoi」関数を使用します。

例:文字列をint型に変換する

std::string s = "123";
int i = std::stoi(s); // 123

例:16真数として文字列をint型に変換する

std::string s = "10";
int i = std::stoi(s, nullptr, 16); //16

std::string s = "0xFF";
int i = std::stoi(s, nullptr, 16); //255

例:8真数として文字列をint型に変換する

std::string s = "10";
int i = std::stoi(s, nullptr, 8); //8

文字列をfloat型に変換する

文字列をfloat型に変換するには「std::stof」関数を使用します。

例:文字列をfloat型に変換する

std::string s = "12.3";
float f = std::stof(s); //12.3

例:指数表現の文字列をfloat型に変換する

std::string s = "1.234e2";
float f = std::stof(s); //123.4

デザイン時に非ビジュアルコンポーネントを非表示にするには

Delphi/C++Builderでは、実行時には表示されない非ビジュアルコンポーネントもフォーム上に配置します。

フォームに配置した非ビジュアルコンポーネントは、フォームをデザインするときに邪魔になることがあります。

フォームに配置した非ビジュアルコンポーネントを一時的に非表示にする方法を紹介します。

nonvisualcomponents1

nonvisualcomponents3

最初にGExpertsがインストールされていない場合は、GExpertsをインストールします。

メニューの「GExperts」から「Hide/Show Non-Visual」を選択すると、非ビジュアルコンポーネントの表示・非表示を切り替えることができます。

nonvisualcomponents2

C++BuilderからHmJre.dllのReplaceRegularNoCaseSense関数を使う

C++Builder XE5からHmJre.dllのReplaceRegularNoCaseSense関数を使う方法です。

先ほどのエントリー「DelphiからHmJre.dllのReplaceRegularNoCaseSense関数を使う」のC++Builder版になります。

/**
 * 特定の文字列から特定の正規表現パターンのマッチングをして、
 * ヒットした場合はパラメータに従った置換を実行し、
 * その置換した結果の文字列を返します。
 */
typedef char* WINAPI(*TReplaceRegularNoCaseSense)(
    // 検索したい正規表現文字列
    char* pszPattern,
    // 検索対象の文字列
    char* pszTarget,
    // 検索を開始する桁位置
    int iStart,
    // 検索でヒットした文字列を置換する用の文字列
    char* pszReplace,
    // 0.最初にヒットした文字を置換、1.ヒットしたすべての文字を置換
    int iMode);

void __fastcall TForm1::Button1Click(TObject *Sender) {
    HMODULE dll = LoadLibrary(L"HmJre.dll");
    TReplaceRegularNoCaseSense ReplaceRegularNoCaseSense =
        reinterpret_cast<TReplaceRegularNoCaseSense>(GetProcAddress(dll, "ReplaceRegularNoCaseSense"));
    AnsiString s = ReplaceRegularNoCaseSense("([a-z]+)([0-9]+)",
        "--abcdefg1234567--", 0, "\\2\\1", 1);
    ShowMessage(s);
    FreeLibrary(dll);
}

constのstd::mapに対してoperator[]を呼び出すとコンパイルエラー

constのstd::mapに対してoperator[]を呼び出すとコンパイルエラーになる

int f(const std::map<int, int>& m, int x) {
  return m[x]; //=>コンパイルエラー
}

std::mapのoperator[]は次のように定義されていて、constメソッドではない。

Ty& operator[](const Key& keyval);

C++11ではat()メンバ関数がstd::mapに追加された。

次のようにコードを記述できる。

int f(const std::map<int, int>& m, int x) {
  return m.at(x);
}

ちなみに、C++Builder XE5の64ビットコンパイラはC++11に対応しているが、32ビットコンパイラは対応していない。

constメンバ関数が欲しい

C++のconstメンバ関数は、メンバ変数が変更されないことを保証する関数です。
constメンバ関数を使えば、プログラマの意図しないオブジェクトの変更がなくなります。

constメンバ関数の宣言は次のようになります。

struct TSample
{
  //普通のメンバ関数
  void Bar();
  //constメンバ関数
  void Baz() const;
};

constなオブジェクトのconstでないメンバ関数を呼ぶとコンパイルエラーになります。

void Func1(const TSample& Sample) { Sample.Bar(); }
//=> コンパイル失敗
//=> E2522 const オブジェクトに対して非 const 関数 TSample::Bar() が呼び出された

constなオブジェクトのconstメンバ関数は呼ぶことができます。

void Func2(const TSample& Sample) { Sample.Baz(); }
//=>コンパイル成功

プログラマの意図しないオブジェクトの変更がなくなり、思わぬバグを未然に防ぐことができます。
とくに複数人のプロジェクトや、他人が作ったコードを使用するときはとても役に立ちます。

Java・C#・Delphiには、constメンバ関数がないようです。
constメンバ関数はとてもありがたい機能なので、他の言語でも実装してもらいたいものです。

FirebirdのUDFを作成してみた (2)UDFの引数で文字列を受け取る

前回(FirebirdのUDFを作成してみた)の続き。

Firebirdで自作UDFを作る時に、引数で文字列を受け取る方法を調べました。

C++でUDFを作るなら、Firebirdの「examples/udf/」にあるfbudfを参考にすると良さそうです。

「examples/udf/fbudf.sql」を見ると、varchar型は「paramdsc*」という型で引数として受け取っています。

--FBUDF_API paramdsc* sNvl(paramdsc* v, paramdsc* v2, paramdsc* rc)
declare external function snvl
varchar(100) by descriptor, varchar(100) by descriptor,
varchar(100) by descriptor returns parameter 3
entry_point 'sNvl' module_name 'fbudf';

「paramdsc*」型は「include/ibase.h」に定義があります。

typedef struct paramdsc {
    ISC_UCHAR  dsc_dtype;
    signed char    dsc_scale;
    ISC_USHORT    dsc_length;
    short    dsc_sub_type;
    ISC_USHORT    dsc_flags;
    ISC_UCHAR  *dsc_address;
} PARAMDSC;

「paramdsc*」型で受け取った引数から、実際の文字列を取得するには「examples/udf/fbudf.cpp」にあるinternal::get_any_string_type()関数を使用します。
この関数はtextに文字列を代入して、文字列長(バイト数)を返します。

namespace internal
{
  int get_any_string_type(const paramdsc* v, ISC_UCHAR*& text)
  {
    …
  }
}

なお、「ISC_UCHAR」型はは「include/ibase.h」で定義されています。

typedef unsigned char   ISC_UCHAR;

使用例

extern "C" __declspec(dllexport) int sample_func(const paramdsc* v) {
  ISC_UCHAR* text;
  const int len = internal::get_any_string_type(v, text);

文字列の文字コードは、データベースの文字コードになるようです。
自分で使う時はUTF-8の決めうちで問題ありませんが、汎用的な関数を作る時は難しいかもしれません。

C++のboost::filesystemでファイルやディレクトリを操作する

C++のboost::filesystemでファイルやディレクトリを操作する方法。

ファイルのコピー

#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>

try {
  boost::filesystem::path src("C:\\sample\\src.txt");
  boost::filesystem::path dst("C:\\sample\\dst.txt");
  boost::filesystem::copy_file(src, dst);
} catch (std::exception& e) {
  std::cout << e.what() << std::endl;
}

ファイルの削除

#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>

boost::filesystem::path src("C:\\sample\\src.txt");
if (boost::filesystem::remove(src)) {
  std::cout << "ok" << std::endl; //削除した
}

ファイル名の変更

#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>

boost::filesystem::path src("C:\\sample\\src.txt");
boost::filesystem::path dst("C:\\sample\\dst.txt");
try{
  boost::filesystem::rename(src, dst);
} catch (std::exception& e) {
  std::cout << e.what() << std::endl;
}

ディレクトリの作成

#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>

boost::filesystem::path dir("C:\\sample\\test");
if (boost::filesystem::create_directory(dir)) {
  std::cout << "ok" << std::endl; //ディレクトリの作成に成功した
}

ディレクトリの削除(ファイルの削除と同じ)

#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>

boost::filesystem::path dir("C:\\sample\\test");
if (boost::filesystem::remove(dir)) {
  std::cout << "ok" << std::endl; //削除した
}

※ディレクトリが空でない時は削除に失敗する。次の「ディレクトリの内容の取得」でディレクトリを空にしてから削除すること。

ディレクトリの内容の取得

#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>

boost::filesystem::path dir("C:\\sample");
boost::filesystem::directory_iterator end;
for (boost::filesystem::directory_iterator p(dir); p != end; ++p) {
  std::cout << p->leaf() << std::endl; //ファイル名
  std::cout << p->string() << std::endl; //フルパス
  if (boost::filesystem::is_directory(*p)) {  //ディレクトリの時
    std::cout << "<DIR>" << std::endl;
  }
}

フルパスからファイル名とパス名を取得する

#include <boost/filesystem/path.hpp>

boost::filesystem::path f("C:\\sample\\src.txt");
std::cout << f.leaf() << std::endl; //=> src.txt
std::cout << f.branch_path() << std::endl; //=> C:\sample

C++で一時ファイルを作成する

tmpfile()を使うと一時ファイルを作成することができます。
失敗した時はNULLを返します。

#include <stdio.h>

FILE* pf = tmpfile();
if (pf) { //ファイルの作成に成功した時
  fputs("tempfile", pf);
}
fclose(pf);

tmpnam()は一時ファイルのファイル名を作成します。

#include <stdio.h>

char* filename = tmpnam(NULL);
if (filename) {
  ofstream of(filename);

追記

Windows XPではtmpfile関数を使用するには管理者権限が必要になります。
現実問題としては、使い物になりません。