VS2015のstd::error_categoryが奇想天外な件について

皆さん御存知の通り、C++には``std::error_category``という物がある。

```cpp:win32apiと一緒に使う例
if(/*なんか*/) throw std::system_error(std::error_code(GetLastError(), std::system_category()));
```

例えば``std::system_category()``は``std::error_category``から派生したクラス型のオブジェクトへの参照を返す。

おなじみcppjpの解説は

>http://cpprefjp.github.io/reference/system_error/system_category.html
>error_categoryクラスは、エラー情報を分類するための基本クラスである。

>エラーコードから対応するエラーメッセージを取得する処理が異なる場合などで、error_categoryクラスを派生して環境固有のエラー情報を取得するためのクラスを定義できる。

となっている。

# まずはデフォルトコンストラクタでも見てみるか

## mingw-gcc6.1.0の場合
```cpp:mingw-gcc6.1.0の場合
namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION
  inline namespace _V2 {
  class error_category
  {
  public:
    constexpr error_category() noexcept = default;
  };
  } // end inline namespace
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace
```

まあそうだよな。

## VS2013の場合
```cpp:VS2013の場合
_STD_BEGIN
class error_category
	{	// categorize an error
public:
	error_category()
		{	// default constructor
		}
	};
_STD_END
```

VS2013はconstexprに対応していないからまあそうなるわな。

## VS2015の場合

```cpp:VS2015の場合
_STD_BEGIN
class error_category
	{	// categorize an error
public:
	/* constexpr */ error_category() _NOEXCEPT	// TRANSITION
		{	// default constructor
		_Addr = reinterpret_cast<uintptr_t>(this);
		}
	};
_STD_END
```

あれ、気がついたらいつの間にか開いていた規格書ではconstexprにしろと書いてあるんだけど、なってないな・・・。
・・・ん？**``_Addr``ってなんだ。**

# VS2015の実装を全部見てみる。

```cpp:VS2015の場合
_STD_BEGIN
class error_category
	{	// categorize an error
public:
	/* constexpr */ error_category() _NOEXCEPT	// TRANSITION
		{	// default constructor
		_Addr = reinterpret_cast<uintptr_t>(this);
		}
	virtual ~error_category() _NOEXCEPT
		{	// destroy the object
		}

	virtual const char *name() const _NOEXCEPT = 0;

	virtual string message(int _Errval) const = 0;

	virtual error_condition
		default_error_condition(int _Errval) const _NOEXCEPT;

	virtual bool equivalent(int _Errval,
		const error_condition& _Cond) const _NOEXCEPT;

	virtual bool equivalent(const error_code& _Code,
		int _Errval) const _NOEXCEPT;

	bool operator==(const error_category& _Right) const _NOEXCEPT
		{	// compare categories for equality
		return (_Addr == _Right._Addr);
		}

	bool operator!=(const error_category& _Right) const _NOEXCEPT
		{	// compare categories for inequality
		return (!(*this == _Right));
		}

	bool operator<(const error_category& _Right) const _NOEXCEPT
		{	// compare categories for order
		return (_Addr < _Right._Addr);
		}

	error_category(const error_category&) = delete;
	error_category& operator=(const error_category&) = delete;

protected:
	uintptr_t _Addr;

	enum : uintptr_t
		{	// imaginary addresses for Standard error_category objects
		_Future_addr = 1,
		_Generic_addr = 3,
		_Iostream_addr = 5,
		_System_addr = 7
		};
	};
_STD_END
```

どうやら比較演算子で使われているらしい。

```cpp:VS2015の実装から比較演算子を抜粋
_STD_BEGIN
class error_category
	{	// categorize an error
	bool operator==(const error_category& _Right) const _NOEXCEPT
		{	// compare categories for equality
		return (_Addr == _Right._Addr);
		}
	bool operator<(const error_category& _Right) const _NOEXCEPT
		{	// compare categories for order
		return (_Addr < _Right._Addr);
		}
	};
_STD_END
```

ちょっとまって、規格書を見に行こう。

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf

>§ 19.5.1.1
#### 19.5.1.1 Class error_category overview [syserr.errcat.overview]

>
1. The class ``error_category`` serves as a base class for types used to identify the source and encoding of a
particular category of error code. Classes may be derived from ``error_category`` to support categories of
errors in addition to those defined in this International Standard. Such classes shall behave as specified in
this subclause. [ Note: ``error_category`` objects are passed by reference, and **two such objects are equal
if they have the same address. This means that applications using custom error_category types should
create a single object of each such type.** —end note ]

>§ 19.5.1.3

> 2
>
```cpp
bool operator==(const error_category& rhs) const noexcept;
```
Returns: **``this == &rhs``.**

>4

>
```cpp
bool operator<(const error_category& rhs) const noexcept;
```

>Returns: **``less<const error_category*>()(this, &rhs)``.**
[ Note: less (20.9.6) provides a total ordering for pointers. —end note ]


デフォルトコンストラクタはthisポインタを保存しているみたいだけど・・・なんでそんなことを？

# std::system_categoryを調べてみる

この謎をとくために、``std::system_category``という関数を見てみよう。

```cpp
_STD_BEGIN
template<class _Ty> inline
	_Ty& _Immortalize()
	{	// return a reference to an object that will live forever
	/* MAGIC */ static _Immortalizer<_Ty> _Static;
	return (*reinterpret_cast<_Ty *>(&_Static._Storage));
	}
inline const error_category& system_category() _NOEXCEPT
	{	// get system_category
	return (_Immortalize<_System_error_category>());
	}
_STD_END
```

``_Immortalize``は規格書の要求を満たすためにstatic storageにクラスを構築するためのものなんだろう。(よく見たら微妙に違うみたいだけど)

```cpp
_STD_BEGIN
		// CLASS _System_error_category
class _System_error_category
	: public error_category
	{	// categorize an operating system error
public:
	_System_error_category() _NOEXCEPT
		{	// default constructor
		_Addr = _System_addr;
		}

	virtual const char *name() const _NOEXCEPT
		{	// get name of category
		return ("system");
		}

	virtual string message(int _Errcode) const
		{	// convert to name of error
		const unsigned long _Size = 32767;
		string _Narrow(_Size, '\0');

		const unsigned long _Val = _Winerror_message(
			static_cast<unsigned long>(_Errcode), &_Narrow[0], _Size);

		if (_Val == 0)
			return ("unknown error");

		_Narrow.resize(_Val);
		_Narrow.shrink_to_fit();
		return (_Narrow);
		}

	virtual error_condition
		default_error_condition(int _Errval) const _NOEXCEPT
		{	// make error_condition for error code (generic if possible)
		int _Posv = _Winerror_map(_Errval);

		if (_Posv != 0)
			return (error_condition(_Posv, generic_category()));
		else
			return (error_condition(_Errval, system_category()));
		}
	};
_STD_END
```

ん！？なんかデフォルトコンストラクタがおかしいぞ・・・？

```cpp
_STD_BEGIN
class _System_error_category
	: public error_category
	{	// categorize an operating system error
	_System_error_category() _NOEXCEPT
		{	// default constructor
		_Addr = _System_addr;
		}
_STD_END
```

``_System_addr``ってなんだ？

・・・そういえば``std::error_category``の実装を載せた時にへんなenumがあったな

```cpp
_STD_BEGIN
class error_category
	{	// categorize an error
protected:
	enum : uintptr_t
		{	// imaginary addresses for Standard error_category objects
		_Future_addr = 1,
		_Generic_addr = 3,
		_Iostream_addr = 5,
		_System_addr = 7
		};
	};
_STD_END
```


あった。ご丁寧にコメントが書いてある。

>imaginary addresses for Standard error_category objects

Google先生お願いします。

>標準error_categoryオブジェクトの架空のアドレス

ほう・・・。つまり、``std::error_category``クラスの比較演算子は、このenumによって作られた仮想的なアドレスを比較していると。
・・・いやそれってだめなんじゃ？もう一度規格書をみるよ。

>§ 19.5.1.1
#### 19.5.1.1 Class error_category overview [syserr.errcat.overview]

>
1. The class ``error_category`` serves as a base class for types used to identify the source and encoding of a
particular category of error code. Classes may be derived from ``error_category`` to support categories of
errors in addition to those defined in this International Standard. Such classes shall behave as specified in
this subclause. [ Note: ``error_category`` objects are passed by reference, and **two such objects are equal
if they have the same address. This means that applications using custom error_category types should
create a single object of each such type.** —end note ]

ここでいう``address``はつまるところポインタのことじゃないのか？

cppjpでの @faithandbrave@github さんの解説も

>http://cpprefjp.github.io/reference/system_error/error_category/op_less.html

>
```cpp
bool operator<(const error_category& rhs) const noexcept;
```

>error_categoryオブジェクトのポインタの小なり比較を行う。

>自身のポインタがrhsオブジェクトへのポインタより小さい場合trueを返し、そうでなければfalseを返す。

と書いている。

# 結論

VS2015は、それが規格的にいいのかは知らんが、``std::error_category``の比較演算子の実装に仮想的なアドレスを使っている。

このせいで自分で``std::error_category``の派生クラスを作るときに困る。

皆さんに聞きたい、この実装ってありなの？

# 追記:VS2015で仕様が変わった理由らしきもの

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr"><a href="https://t.co/MJo8E4d3yg">https://t.co/MJo8E4d3yg</a><br>ここの &lt;system_error&gt; Fixes の2番目かな<br>  <a href="https://t.co/MUzHX3H5zk">https://t.co/MUzHX3H5zk</a></p>&mdash; おいがみ (@oigami013) <a href="https://twitter.com/oigami013/status/770248522603954176">2016年8月29日</a></blockquote>

ん！？

># STL Fixes In VS 2015, Part 2 | Visual C++ Team Blog
>https://blogs.msdn.microsoft.com/vcblog/2015/07/14/stl-fixes-in-vs-2015-part-2/
>* Error category objects didn’t behave properly across different DLLs/EXEs (DevDiv#666062, DevDiv#1095970/[Connect#1053790](https://connect.microsoft.com/VisualStudio/feedback/details/1053790)).  The tale of woe here was complicated.  Calling generic_category(), for example, is supposed to return a reference to a single unique object, regardless of where it’s called.  This is usually achieved by separate compilation into the STL’s DLL (or static LIB).  However, we can’t separately compile error_category machinery, because it has a virtual message() returning std::string, whose representation is affected by _ITERATOR_DEBUG_LEVEL.  So, generic_category() is implemented header-only – but that means that different user DLLs end up with different instantiations and therefore different objects.  (It’s also possible for this to cause trouble between a user’s EXE and the STL’s DLL.)  We fixed this to achieve as much conformance as possible.  We’ve taught error_category, its derived classes, and its operator==()/operator!=() to consider all generic_category() objects to be equal, even if they live at different addresses in different DLLs (and similarly for the other error category objects in the Standard).  This has been implemented so that user-defined error category objects will be unaffected.  The only thing we can’t fix is a direct comparison of error_category addresses (code should use operator==() instead). 

>#[C++] Comparison std::error_code from std::system_error to std::errc enum value incorrect when using DLL-version of run-time library
>https://connect.microsoft.com/VisualStudio/feedback/details/1053790
>A comparison of the error code from an std::system_error object thrown by std::thread to std::errc::resource_unavailable_try_again appears to produce an incorrect Boolean result when the application uses a DLL-specific version of the run-time library (compiler option "/MD" or "MDd").

>The issue can be reproduces by the attached file, "MyTest.cpp", or by the online C++ compiler at http://rextester.com/EMVQXX20044

>Christopher Kohlhoff encouraged me to submit this bug report. He suggested that the issue might be caused by the fact that the implementation compares error categories by their address, and from MSVC

うーん、英語が得意じゃない私にはちときつい・・・。
dllにしたところ、アドレスがdll使用側と一致しないぞい、ってこと？(あやふや)