2012年1月15日日曜日

Windows 上での Boost.Interprocess の named_mutex の挙動

自分が改造して公開している ax7z_s.spi のα版では(必要もないのですが) Boost.Interprocess をほんの一部だけ使用しています。その中でちょっと想定外の挙動があったのでメモしてみます。使用しているのは named_mutex です。Windows 上だと Mutex に対するラッパになっている……と思っていたのですが実はそうなっていません(少なくとも 1.48 までは)。共有メモリを使ってエミュレーションされています。では、それがどのような違いを生むのか簡単に見てみましょう。まずは Win32 API 版。

Locker by Win32 API

#include <windows.h>
#include <iostream>

int main(void)
{
 HANDLE hMutex = CreateMutex(0, TRUE, "cx.myhome.yak.test"); // Initial owner
 char c; std::cin >> std::noskipws >> c;
 CloseHandle(hMutex);
 return 0;
}

Waiter by Win32 API

#include <windows.h>
#include <iostream>

int main(void)
{
 HANDLE hMutex = CreateMutex(0, FALSE, "cx.myhome.yak.test");
 char c; std::cin >> std::noskipws >> c;
 DWORD dwResult = WaitForSingleObject(hMutex, INFINITE);
 std::cout << dwResult << std::endl;
 ReleaseMutex(hMutex);
 CloseHandle(hMutex);
 return 0;
}

Locker 起動→Waiter 起動→Locker に ENTER 入力→Waiter で ENTER 入力、した場合の挙動は次の図のようになります。

Locker が Mutex を獲得したまま終了しますが、この時点で Wait している Waiter 側には WAIT_ABANDONED(256) が返ってきます。Mutex を獲得していたプロセスが死んだことを通知するための値ですが、WAIT_OBJECT_0(0) が返ってきたかのように無視すればそのまま動作します。一方、Locker 起動→Locker に ENTER 入力→Waiter 起動→Waiter で ENTER 入力、した場合の挙動は次の図のようになります。

こちらでも Locker が Mutex を獲得したまま終了しますが、この時点で Mutex に対するハンドルがないので Mutex オブジェクトが消滅します(多分)。結果、Locker を起動した際には何事もなく WAIT_OBJECT_0(0) が返ってきます。

いずれにしろ、Locker 側が Mutex を獲得したまま終了しても、Waiter 側は問題なく実行できる、というところがポイントです。

それでは Boost Interprocess を使った場合はどうなるでしょうか?

Locker by Boost.Interprocess

#include <boost/interprocess/sync/named_mutex.hpp>
#include <iostream>

int main()
{
 using namespace boost::interprocess;
 named_mutex mutex(open_or_create, "cx.myhome.yak.test");
 mutex.lock();
 char c; std::cin >> std::noskipws >> c;
 return 0;
}

Waiter by Boost.Interprocess

#include <boost/interprocess/sync/named_mutex.hpp>
#include <iostream>

int main()
{
 using namespace boost::interprocess;
 named_mutex mutex(open_or_create, "cx.myhome.yak.test");
 char c; std::cin >> std::noskipws >> c;
 mutex.lock();
 mutex.unlock();
 return 0;
}

Locker 起動→Waiter 起動→Locker に ENTER 入力→Waiter で ENTER 入力、した場合の挙動は次の図のようになります。

Locker が Mutex を獲得したまま終了しますが、Win32 API の場合と異なり獲得された状態のまま残ってしまいます。そのため、Locker はデッドロックします。ちゃんと解放するように組めばいい、と思われるかもしれませんが、強制終了させた場合なんかもデッドロックしてしまうため結構厳しいです。Locker 起動→Locker に ENTER 入力→Waiter 起動→Waiter で ENTER 入力、した場合の挙動も次のようになります。

こちらの場合でも Mutex は獲得された状態のまま残ってしまい、Locker がデッドロックします。

どうしてこうなるか、というのは named_mutex の実装に由来します。上では共有メモリを使っていると書きましたが正確にはファイルマッピングを使っています。名前との対応関係を容易にするためでしょう実際にファイルと結びつけられたものです。c:\ProgramData あるいは c:\Documents and Settings\All Users\Application Data フォルダ以下に boost_interprocess というフォルダが作成され、さらにその下に 20120102003353.109999 のような名前のフォルダが起動時刻を元に作成されます。この下に指定された名前(上の例の場合なら cx.myhome.yak.test)のファイルができます。これが Mutex の実体です。特に9~12バイト目の部分が Mutex の状態を表しており、1 なら獲得状態、0なら非獲得状態です。このファイルが獲得状態のまま残り続けるためずっとデッドロックすることになります。もちろんこのファイルを削除してから起動してやればロックからは脱出できます。また、再起動後に Waiter 等を実行すると起動時刻を元に作成するフォルダが再作成され古いフォルダは削除されるのでロック状態からは脱出できるようになっています。つまり Boost.Interprocess のデータは破棄しない限り再起動するまでの間有効ということです。

さて、最後にもう一つ。Boost.Interprocess の named_mutex の例の項では remover というものがあります。大抵の C++er は「デストラクタで始末すればいいのになんで別に始末するためのものを用意する必要があるんだろう?」と思われるのではないでしょうか。理由は恐らく「共有されている named_mutex の実体」を削除するタイミングは named_mutex 各オブジェクトからは分からないためだと思われます。デストラクタで行っている理由は例外送出時の対応と、順序(named_mutex オブジェクト破棄後に remove)のため……だと思ったのですが、実は順序は named_mutex 破棄後に remove でなくとも動作します。

#include <boost/interprocess/sync/named_mutex.hpp>
#include <iostream>

int main()
{
 using namespace boost::interprocess;
 named_mutex mutex(open_or_create, "cx.myhome.yak.test");
 mutex.lock();
 char c; std::cin >> std::noskipws >> c;
 named_mutex::remove("cx.myhome.yak.test");
 std::cin >> std::noskipws >> c;
 return 0;
}

上のコードを実行し、boost_interprocess フォルダの中を覗いてみると cx.myhome.yak.test という名前のファイルが出来ておりラスト 4 byte は 1 になっているはずです。 1 回 ENTER を打った後だと named_mutex オブジェクトが存在する状態で remove していることなりますが、cx.myhome.yak.test はなくなり、60FEE8D7B3C8CC018964BC3FCED2CC01 みたいな謎のファイル名に変わっているはずです。これは削除準備状態でこのファイル自体を開くことはできません。元ファイルを参照しているハンドルが閉じられた場合(この場合 named_mutex が破棄された場合)削除されます。ちなみにこの削除準備状態で Waiter を実行して ENTER を打つと cx.myhome.yak.test は存在しないためデッドロックせず問題なく終了します。この後、再度 ENTER を打つと named_mutex が破棄され削除準備状態だったファイルが削除されます。逆に言えば remove は問答無用で実体を削除してしまうため使うタイミングは良く考えるべきだと言えると思います。

さて、長々書いてきましたが、どうやらこれ、POSIX の場合に使用される POSIX semaphore の挙動と一致しているようです。そしてプロセスが死ぬようなケースでは POSIX semaphore を使わず OS が勝手に解放してくれるファイルロック等を使え、ということのようです。参考 URL:

2011年12月28日水曜日

【C++11】 Designated Initializer 風な何か Boost.Fusion 対応

はい、という訳で当初目標であった、Designated Initializer 風な何かの Boost.Fusion 対応ができました。実際のコードは https://github.com/yak1ex/cpp_stuff にあります。GCC 4.7 専用です。書いた後で言うのもなんですが、まぁ余程でかい構造体でなければ全部初期化子書いた方が早いだろ、という気はします。あと最初っからやる気があるなら Boost.Parameter 使えばいいんじゃね?、みたいな。配列の場合は意味があるケースはあるかもしれません。特定の添字だけじゃなくて range<5, 12>() = 1 と書くと 5 ~ 11 が 1 で初期化とかだとより意味があるケースが出てくるかも。

テストコード

#include <vector>
#include <iostream>

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/define_struct.hpp>
#include <boost/array.hpp>
#include <boost/fusion/include/boost_array.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/fusion/include/boost_tuple.hpp>
#include <boost/fusion/include/adapt_assoc_struct.hpp>

#include <boost/fusion/include/out.hpp>

#include "dinit.hpp"

// Helper function

template<typename T>
void output(const T& v)
{
 for(auto val : v) {
  std::cout << ' ' << val;
 }
 std::cout << std::endl;
}

// Type definition and adapter

// Don't work for BOOST_FUSION_DEFINE_STRUCT becaues conflict with constructor

struct employee
{
 std::string name;
 int age;
 double rate;
};

BOOST_FUSION_ADAPT_STRUCT(
 employee,
 (std::string, name)
 (int, age)
 (double, rate)
)

namespace keys {
 struct name;
 struct age;
 struct rate;
}

struct employee_
{
 std::string name;
 int age;
 double rate;
};


BOOST_FUSION_ADAPT_ASSOC_STRUCT(
 employee_,
 (std::string, name, keys::name)
 (int, age, keys::age)
 (double, rate, keys::rate)
)

// Don't work for plain array because brace-enclosed initializer is required.

int main(void)
{
 using yak::util::dinit::di;
 using yak::util::dinit::idx;
 using yak::util::dinit::idx_;
 using yak::util::dinit::key;
 using yak::util::dinit::key_;

// For sequence with list constructor

 std::vector<int> v = di(idx<10>() = 1, idx<0>() = 2, idx<2>() = 3, idx<3>() = 4);
 output(v);

 std::vector<int> v2 = di(idx_<10>(1), idx_<0>(2), idx_<2>(3), idx_<3>(4));
 output(v2);

// For Fusion sequence with idx

 employee e = di(idx<2>() = 3.3, idx<1>() = 33);
 boost::fusion::out(std::cout, e); std::cout << std::endl;

 boost::array<int, 5> ar2 = di(idx<3>() = 4);
 output(ar2);

 boost::tuple<std::string, int, double> t = di(idx<1>() = 5);
 boost::fusion::out(std::cout, t); std::cout << std::endl;

// For Fusion sequence with tag

 employee_ e2 = di(key<keys::rate>() = 3.3, key<keys::age>() = 33);
 boost::fusion::out(std::cout, e2); std::cout << std::endl;

 employee_ e3 = di(key_<keys::rate>(3.3), key_<keys::age>(33));
 boost::fusion::out(std::cout, e3); std::cout << std::endl;

 return 0;
}

出力

 2 0 3 4 0 0 0 0 0 0 1
 2 0 3 4 0 0 0 0 0 0 1
( 33 3.3)
 0 0 0 4 0
( 5 0)
( 33 3.3)
( 33 3.3)

2011年12月25日日曜日

【C++11】 Designated Initializer 風な何か シーケンス用

元々、【C++11 Advent Calendar 2011】17日目 小ネタ集 後編用にネタコード作ろうと思ってちょこちょことやっていた訳ですが間に合わなさそうだったので、16進浮動小数点定数やってみたら割とさくっとできたのでそっちだけ記事に書いたわけですが。とりあえずそれっぽいものができたので公開してみます。https://github.com/yak1ex/cpp_stuff の、dinit.hpp, index_tuple.hpp, dinit.cpp になります。とりあえず GCC 4.7 -std=c++0x 限定です。自分が書いてるコードが悪いような気がしますが。後どこかで dangling reference ってる気もします。

#include <vector>
#include <iostream>

#include "dinit.hpp"

int main(void)
{
 using yak::util::dinit::di;
 using yak::util::dinit::idx;
 using yak::util::dinit::idx_;

 std::vector<int> v = di(idx<10>() = 1, idx<0>() = 2, idx<2>() = 3, idx<3>() = 4);
 for(auto val : v) {
  std::cout << val << std::endl;
 }
 std::vector<int> v2 = di(idx_<10>(1), idx_<0>(2), idx_<2>(3), idx_<3>(4));
 for(auto val : v2) {
  std::cout << val << std::endl;
 }
 return 0;
}

出力

2
0
3
4
0
0
0
0
0
0
1
2
0
3
4
0
0
0
0
0
0
1

見ての通り、di() の中で idx<添字>() = 値、あるいは idx_<添字>(値) を並べるとそれに沿って初期化されます。それに沿って初期化されたものによってコピーないしムーブ初期化されます、の方が正確かもしれません。ただ -O2 の段階でも main() 関数内に初期値(2,0,3,4,0,...,0,1)の push が展開されていたのでほぼ初期化相当と見ていいんじゃないでしょうか。↑は vector ですが、initializer-list constructor 持ってるやつ(deque, list, forward_list)なら動いているようです、多分。配列や array にも使えないという残念仕様ですが。また、普通同じ添字のものがあったら最後が有効になると思うでしょうが、最初のものが有効になります。これは直そうと思えばすぐ直せそうですが。

コード的には di() でテンプレート変換演算子を持っているクラスの一時オブジェクトを作成、テンプレート変換演算子中で list initialization している形になります。できればせめて di{} にしたかったのですが早々に諦めました。イメージ的には↓のような感じでこれに実際に実現するためのあれやこれやをつけた感じです。

template<typename Tuple, std::size_t ... idxN>
struct Initer
{
 constexpr Initer(const Tuple& t) : args(t) {}
 template<typename U>
 constexpr operator U()
 {
  return { /* ... */ };
 }
 Tuple args;
};

template<typename ... T>
constexpr const auto di(T&& ... t) -> /* Initer<std::tuple<...>, ...> */;

【C++11】 decltype, conditional operator, そして common_type

C++11 に限らず C++03 的にも基本的なところから不勉強で「そういうことだったのかー」と思うことが多いのであまり自信がないのですが、規格の不整合じゃないかと思われる点を見つけたので書いてみます。基本的に語尾に「と思います、多分」がついていると解釈ください。

とりあえず decltype(e) について FDIS(n3290、自分が持っているのは変更履歴付きの n3291 です) で以下の変更が加えられています(7.1.6.2/4)。

The type denoted by decltype(e) is defined as follows:
  • if e is an unparenthesized id-expression or an unparenthesized class member access (5.2.5), decltype(e) is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;
  • otherwise, if e is a function call (5.2.2) or an invocation of an overloaded operator (parentheses around e are ignored), decltype(e) is the return type of the statically chosen function; an xvalue, decltype(e) is T&&, where T is the type of e;
  • otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;
  • otherwise, decltype(e) is the type of e.
The operand of the decltype specifier is an unevaluated operand (Clause 5).

decltype((e)) としては関数呼び出し(オーバーロードされた演算子を含む)の場合はその(静的な)返値の型になるという条件付きの記述から、lvalue なら T&、xvalue なら T&&、prvalue なら T、と分かりやすくなったと言えます。さて、この結果、オーバーロードされていない演算子について、xvalue を返すものに対する delctype((e)) は T から T&& へ挙動が変わったことになります。conditional operator (?:) はオーバーロードできないので常にこの場合に入ります。実際のルールはかなりややこしいのですが第2オペランドと第3オペランドが(lvalue, xvalue, prvalue のカテゴリも含めて)同じ型のケースに限ればその同じ型(とカテゴリ)が全体の型(とカテゴリ)になります。従って以下のようになるはずです。

int&  funcL();
int&& funcX();
int   funcPR();

decltype(true ? funcL()  : funcL())  v1 = funcL();  // int&
decltype(true ? funcX()  : funcX())  v2 = funcX();  // int&&
decltype(true ? funcPR() : funcPR()) v3 = funcPR(); // int

さて、規格中で conditional operator と decltype に依存しているのが common_type です。

// n3291 20.2.4
template <class T>
typename add_rvalue_reference<T>::type declval() noexcept;

// n3291 20.9.7.6/3
template <class T, class U>
struct common_type<T, U> {
 typedef decltype(true ? declval<T>() : declval<U>()) type;
};

declval<int>() の返値の型は int&& であり xvalue です。従って common_type<int, int>::type は int && になってしまいます。decltype の挙動変更前は int でした。これがなぜやばいのか。例えば <chrono> にほ以下のような規定があります。

// 20.11.4.3 common_type specializations
template <class Rep1, class Period1, class Rep2, class Period2>
struct common_type<chrono::duration<Rep1, Period1>, chrono::duration<Rep2, Period2>> {
 typedef chrono::duration<typename common_type<Rep1, Rep2>::type, see below> type;
};

// 20.11.5.5, duration arithmetic
template <class Rep1, class Period1, class Rep2, class Period2>
typename common_type<duration<Rep1, Period1>, duration<Rep2, Period2>>::type
constexpr operator+(const duration<Rep1, Period1>& lhs, const duration<Rep2, Period2>& rhs);
// Returns: CD(CD(lhs).count() + CD(rhs).count())

operator+() の返値の型が common_type を使用し、common_type の特殊化でも common_type を使っているため、chrono::duration<int>() + chrono::duration<int>() の返値の型は chrono::duration<int&&> になります。これは規格の規定からも不正です(Rep は arithmetic type ないし arithmetic type をエミュレーションした class でないと駄目です)。また、そもそも自然な利用方法であろう

template<typename T, typename U>
typename std::common_type<T, U>::type operator+(const T& t, const U& u)
{
 return t+u;
}

に対して少なくとも T, U が同一の型の場合に(普通 operator+ は一時オブジェクトを返すため)、返値の型が rvalue reference で一時オブジェクトを束縛してもコンパイルエラーにならず、速攻で dangling reference を生んでしまいます。

自分の解釈だとこうなるのですが、識者の意見が欲しいところです……。

追記

GCC 4.6/4.7 では decltype の挙動が FDIS での修正に沿っていないようで、conditional operator の結果に対する decltype は lvalue の場合 T&(左辺値参照)として、それ以外の場合 T(非参照)として返ってくるようです。その結果 common_type も T(非参照)となり問題は発生しません。

追記2

declval<int>()が抜けていたので訂正しました。

追記3

LWG 2141で同様の指摘が挙げられ修正が決まったようです。

【C++11】 constexpr const

constexpr 楽しいです(挨拶)。先進的な C++er の方々は既に constexpr をばりばり使われていたわけですが「あぁ constexpr、すごいですよねー」みたいな他人事的な感覚でした。が、自分で書いてみると結構楽しいですね。そんなこんなでちょこちょこと書いていたわけですが constexpr const T* func() や constexpr const T& func() ではなく constexpr const T func() という無駄じゃないんですかそれは、というケースでも意味のあるケースが出てきたので書いてみます。

#include <utility>

template<typename T>
struct wrapper
{
 constexpr wrapper() : t{} {}
 constexpr wrapper(const T& t) : t{t} {}
 constexpr wrapper(const wrapper &t) : t{t.t} {}
 wrapper(wrapper &&t) : t{std::move(t.t)} {}
// GCC 4.7 can compile in this case...
// constexpr wrapper(wrapper &&t) : t{std::move(t.t)} {}
 T t;
};

template<typename T>
constexpr wrapper<wrapper<T>> makeT(T t)
{
 return wrapper<wrapper<T>>(wrapper<T>(t));
}

template<typename T>
constexpr const wrapper<wrapper<T>> makeCT(T t)
{
 return wrapper<wrapper<T>>(wrapper<T>(t));
}

int main(void)
{
// Can't compile GCC 4.6/4.7
// constexpr auto p1 = makeT<int>(1).t;
 constexpr auto p2 = makeCT<int>(2).t;
 return 0;
}

なお GCC 4.5 は literal type に対する解釈が違うのでそもそも通りません。const 無い場合は、move constructor が呼ばれて constexpr じゃねぇと怒られます。結局 move を抑止するための const なのですが constexpr 文脈内なら最初っから const 扱いしてくれてもいいんじゃないの?という気はします。規格的にどうなのかも良く分かりませんが。なお、GCC 4.7 だと move constructor に constexpr を付けるとコンパイルできます。もう何が正しいのか良く分かりません。

2011年12月24日土曜日

はてブボタンの追加と Tweet カウントの表示 on Blogger

以下を実施。

  • はてブボタンの追加
  • Tweet カウントの表示(Tweet ボタンの Twitter 公式化)
  • レイアウト調整

↓ diff の位置、行数は正しくない。上側は </head>の直前に追加。前のコンテキストは SyntaxHighlighter 用追加分なので実際にはテンプレートにはない。下側は改行を追加していてテンプレートでは1行になっている。

はてブ公式の Blogger へのボタン追加users 表示追加の説明は古いので適当に修正。他のアイコンと同じように CSS スプライトを使って hover で色付けというのもちらっと考えたが、Google+ のアイコンだけがカラーになるのが気に入らないのでやめ(狭量)。

Twitter 側は元のボタンを殺して公式ボタンを追加。デフォルトテキストはブログタイトル(「や」の字)と記事タイトルが入るようにしている。

後は、上にずれたり、折り返されたりするので、CSS による強制レイアウト調整。

更新(2012/01/01)

Chrome でしか位置が合っていなかったので、IE、Firefox、Opera でも合うように修正。

diff --git a/blogger-template.xml b/blogger-template.xml
--- a/blogger-template.xml
+++ b/blogger-template.xml
@@ -2,7 +2,6 @@
 <!DOCTYPE html>
 <html b:version='2' class='v2' expr:dir='data:blog.languageDirection' xmlns='http://www.w3.org/1999/xhtml' xmlns:b='http://www.google.com/2005/gml/b' xmlns:data='http://www.google.com/2005/gml/data' xmlns:expr='http://www.google.com/2005/gml/expr'>
   <head>
-    <meta content='IE=EmulateIE7' http-equiv='X-UA-Compatible'/>
     <b:if cond='data:blog.isMobile'>
       <meta content='width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0' name='viewport'/>
     <b:else/>
@@ -669,7 +662,39 @@
  SyntaxHighlighter.config.bloggerMode = true;
  SyntaxHighlighter.all();
 </script>
 <!--  end  syntax highlighter -->
+<!-- begin Hatena button -->
+<style type='text/css'><!--
+.hatena-button {
+  height: 20px;
+}
+.hatena-button-image {
+  vertical-align: middle;
+}
+.hatena-users-image {
+  vertical-align: middle;
+}
+.share-button {
+  vertical-align: middle;
+}
+--></style>
+<!--  end  Hatena button -->
+<!-- begin Twitter button -->
+<script src='http://platform.twitter.com/widgets.js' type='text/javascript'/>
+<style type='text/css'><!--
+iframe.twitter-share-button {
+  vertical-align: middle;
+  width: 90px !important;
+}
+--></style>
+<!--  end  Twitter button -->
+<!-- begin adjust Google+ -->
+<style type='text/css'><!--
+div.dummy-container {
+  width: 220px;
+}
+--></style>
+<!--  end  adjust Google+ -->
 
   </head>
 
@@ -920,7 +944,7 @@
   <div class='clear'/>
 </b:includable>
 <b:includable id='shareButtons' var='post'>
   <b:if cond='data:top.showEmailButton'><a class='goog-inline-block share-button sb-email' expr:href='data:post.sharePostUrl + &quot;&amp;target=email&quot;' expr:title='data:top.emailThisMsg' target='_blank'><span class='share-button-link-text'><data:top.emailThisMsg/></span></a></b:if><b:if cond='data:top.showBlogThisButton'><a class='goog-inline-block share-button sb-blog' expr:href='data:post.sharePostUrl + &quot;&amp;target=blog&quot;' expr:onclick='&quot;window.open(this.href, \&quot;_blank\&quot;, \&quot;height=270,width=475\&quot;); return false;&quot;' expr:title='data:top.blogThisMsg' target='_blank'><span class='share-button-link-text'><data:top.blogThisMsg/></span></a></b:if>
-  <b:if cond='data:top.showTwitterButton'><a class='goog-inline-block share-button sb-twitter' expr:href='data:post.sharePostUrl + &quot;&amp;target=twitter&quot;' expr:title='data:top.shareToTwitterMsg' target='_blank'><span class='share-button-link-text'><data:top.shareToTwitterMsg/></span></a></b:if>
+  <!-- disabled for Twitter own button <b:if cond='data:top.showTwitterButton'><a class='goog-inline-block share-button sb-twitter' expr:href='data:post.sharePostUrl + &quot;&amp;target=twitter&quot;' expr:title='data:top.shareToTwitterMsg' target='_blank'><span class='share-button-link-text'><data:top.shareToTwitterMsg/></span></a></b:if> -->
   <b:if cond='data:top.showFacebookButton'><a class='goog-inline-block share-button sb-facebook' expr:href='data:post.sharePostUrl + &quot;&amp;target=facebook&quot;' expr:onclick='&quot;window.open(this.href, \&quot;_blank\&quot;, \&quot;height=430,width=640\&quot;); return false;&quot;' expr:title='data:top.shareToFacebookMsg' target='_blank'><span class='share-button-link-text'><data:top.shareToFacebookMsg/></span></a></b:if><b:if cond='data:top.showOrkutButton'><a class='goog-inline-block share-button sb-orkut' expr:href='data:post.sharePostUrl + &quot;&amp;target=orkut&quot;' expr:title='data:top.shareToOrkutMsg' target='_blank'><span class='share-button-link-text'><data:top.shareToOrkutMsg/></span></a></b:if>
+  <!-- begin AddHatena -->
+    <a class='goog-inline-block hatena-button' expr:href='&quot;http://b.hatena.ne.jp/entry/add/&quot; + data:post.url'>
+      <img class="hatena-button-image" src="画像URL" width="16" height="12" alt="このエントリーをはてなブックマークに追加" title="このエントリーをはてなブックマークに追加" />
+    </a>
+   
+    <a class='goog-inline-block hatena-button' expr:href='&quot;http://b.hatena.ne.jp/entry/&quot; + data:post.url'>
+      <img class="hatena-users-image" 
+       expr:src='&quot;http://b.hatena.ne.jp/entry/image/&quot; + data:post.url'
+       expr:alt='&quot;はてなブックマーク - &quot; + data:post.title'
+       expr:title='&quot;はてなブックマーク - &quot; + data:post.title' />
+    </a>
+  <!-- end AddHatena -->
+  <!-- begin Twitter button -->
+    <a href="https://twitter.com/share" class="twitter-share-button"
+     expr:data-url='data:post.url' expr:data-text='data:blog.title + &quot; &quot; + data:post.title'>
+      <data:top.shareToTwitterMsg/>
+    </a>
+  <!-- end Twitter button -->
   <b:if cond='data:top.showDummy'><div class='goog-inline-block dummy-container'><data:post.dummyTag/></div></b:if>
 </b:includable>
 <b:includable id='threaded_comment_js' var='post'>
   <script defer='defer' expr:src='data:post.commentSrc' type='text/javascript'/>

2011年12月23日金曜日

【C++11】 list-initialization のオーバーロード解決が分からない

当初は、@cpp_akira さんの記事[C++] 波カッコ初期化のススメにかこつけて波カッコ初期化ができないケースについて書こうと思ったのですが、確認のために調べていたら何が正しいのか分からなくなりましたので、その内容を書いてみます。

#include <initializer_list>
struct A
{
 A(std::initializer_list<int>) {} // #1
 A(int n, double d) {}            // #2
};
A a{ 1, 2.5 }; // tries to call #1 but failed, or calls #2?

n3291 13.3.1.7 Initialization by list-initialization p1 には次のような記述があります。

When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor in two phases:
  • Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument.
  • If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.
If the initializer list has no elements and T has a default constructor, the first phase is omitted. In copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.

つまり initializer-list constructor だけを対象としてオーバーロード解決が行われ、viable function が見つからなかった場合に再度全コンストラクタに対してオーバーロード解決が行われます。リストが空の場合はデフォルトコンストラクタが呼ばれます。では先の例はどうなるんでしょうか。Overview of the New C++ (C++11)には次のような例があります。

class Widget {
public:
    Widget(double value, double uncertainty);           // #1
    Widget(std::initializer_list<std::string> values);  // #2
};
double d1, d2;
Widget w1 { d1, d2 }; // tries to call #2; fails because
                      // no double ⇒ string conversion

しかしこれに対しては stackoverflow での質問 C++0x: Overload Resolution規格の最新の変更が未反映との記述があります(Edit-2の所)。で、その質問にもある通り n3291 8.5.4p3 には次のような例があります。

struct S {
 S(std::initializer_list<double>); // #1
 S(const std::string&); // #2
// ...
};
const S& r1 = { 1, 2, 3.0 }; // OK: invoke #1
const S& r2 { "Spinach" }; // OK: invoke #2

これから考えると最初の例でも #2 を呼んで良い気はするのですが、しかしそれは narrowing conversion が #1 を viable にしないならば、です。これに対するコメントが http://lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20110530/042586.html http://lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20110530/042590.html に書かれています。オーバーロード解決で定数式であるかまたその値を確認するの?、あるいは相反してそうな関連する規格の記述について。うーん、どうなんでしょうか。ちなみに g++ 4.6/4.7 ではエラー、警告が出ます。

追記(2011/12/23)

上の ML (llvm の c front-end の commit メールみたいです) の同じスレッド内で解決していたみたいです。http://www.mail-archive.com/cfe-commits@cs.uiuc.edu/msg35510.html(こっちの方がスレッド見やすそうなのでアーカイブの場所を変えました) で URL が挙げられている n2640 Initializer Lists — Alternative Mechanism and Rationale (v. 2) の {}-lists and narrowing では

We propose that if — after deduction, overload resolution, etc. is done — a narrowing conversion (in the sense of N2531) occurs on a {}-enclosed value, the program is ill-formed. This is subtly different from N2531 in that it doesn't affect the existing overload resolution rules.

と記述されており、narrowing conversion の確認はオーバーロード解決の「後」となっています。ということで最初の例では narrowing ですが implicit な conversion があるため #1 が viable function として残り、全コンストラクタに対する再度のオーバーロード解決は行われません。結果として #1 がオーバーロード解決の結果として選択され、implicit narrowing を含むためエラー(ill-formed)となります。一方、Overview of the New C++ (C++11)、及び n3291 の例では implicit conversion が存在しないため initializer-list constructor が viable function とならず全コンストラクタに対する再度のオーバーロード解決が行われることになります。なお、ML の commit 対象のコードも修正されており、std::initializer_list を引数にとる initializer-list constructor を持つ class (template) A に対して

     // Narrowing conversions don't affect viability. The next two choose
     // the initializer_list constructor.
     { A<3> a{1, 1.0}; } // expected-error {{narrowing conversion}}
     { A<3> a = {1, 1.0}; } // expected-error {{narrowing conversion}}

と、いう風に記述されています。