yohhoyの日記

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

無名(匿名)名前空間の不思議な定義

プログラミング言語C++における無名名前空間(unnamed namespace)*1の、一見すると不思議な(実は根拠がある)定義に関するメモ。本記事はStack Overflow上での質問と回答内容に基づく。

C++言語仕様での定義

C++03 7.3.1.1/p1では、unnamed namespaceの振る舞いを次の通り定義する。C++11(N3337) 7.3.1.1/p1でも同一*2

An unnamed-namespace-definition behaves as if it were replaced by

namespace unique { /* empty body */ }
using namespace unique;
namespace unique { namespace-body }

where all occurrences of unique in a translation unit are replaced by the same identifier and this identifier differs from all other identifiers in the entire program. (snip)

下記のような単純な定義となっていない理由に、「最初のunnamed namespace内から、同namespaceで定義された名前(A)をグローバル名前空間の修飾名(::A)で参照」する問題が挙げられる。(具体例は後述)

namespace unique { namespace-body }
using namespace unique;

次コードのunnamed namespaceは、一意な名前空間(仮にXXXとする)を用いたコードと等価に(as if)振る舞う。つまり::Aによって、グローバル名前空間(global namespace)に取り込まれた名前XXX::Aに正しくアクセスできる。

namespace {
  typedef int A;
  ::A a0;
}
namespace {
  ::A a1;
}
// これは下記コードと等価
namespace XXX { }
using namespace XXX;  // 名前空間XXXをグローバル名前空間へ取り込む
namespace XXX {
  typedef int A;
  ::A a0;  // OK: "XXX::A a0;"と等価
}
namespace XXX { }
using namespace XXX;
namespace XXX {
  ::A a1;  // OK: "XXX::A a1;"と等価
}

仮に現行C++言語仕様とは異なる簡易定義とすると、前掲unnamed namespaceは下記コードと等価になる。つまりa0の箇所ではill-formedとなるがa1の箇所ではwell-formedとなり、全体として一貫性のない振る舞いになってしまう。

namespace XXX {
  typedef int A;
  ::A a0;  // NG: この時点で::Aは存在しない!
}
using namespace XXX;
namespace XXX {
  ::A a1;  // OK: "::A"は"XXX::A"を指す
}
using namespace XXX;

gcc, Clang

gcc 4.5.4, 4.6.3, 4.7.2、およびclang 3.1でそれぞれコンパイルできることを確認。

namespace {
  typedef int A;
  ::A a0;
}

VisualC++

MSVCにおけるunnamed namespaceの扱いについて、2012年11月現在のMSDNではC++言語仕様通りでない簡易定義で説明されている。実際にVisual Studio 2012(MSVC11)では下記コードがコンパイルエラーになる。

namespace {
  typedef int A;
  ::A a0;  // NG
  // error C2039: 'A' : '`global namespace'' のメンバーではありません。
}

回避策として下記2パターンのように記述すれば、それぞれ期待通りにコンパイルが通ることを確認。

namespace {
  typedef int A;
}
namespace {
  ::A a0;  // OK
}
namespace {}  // 中身は空
namespace {
  typedef int A;
  ::A a0;  // OK
}

*1:"anonymous namespace(匿名名前空間)" と呼称されることもあるが、C++標準規格上は "unnamed namespace" が正式名。

*2:正確には inline namespace の扱いが追加されているが、基本的な振る舞いはC++03と同じ。