<?xml version="1.0" encoding="UTF-8" ?>
<rdf:RDF
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  xmlns="http://purl.org/rss/1.0/"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:content="http://purl.org/rss/1.0/modules/content/">

  <channel rdf:about="http://tk0xleader.blog.shinobi.jp/RSS/100/">
    <title>VIEWブログ</title>
    <link>http://tk0xleader.blog.shinobi.jp/</link>
    <atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://tk0xleader.blog.shinobi.jp/RSS/" />
    <atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" />
    <description>tk-xleaderのブログ。C++などプログラミングの話題が中心です。</description>
    <dc:language>ja</dc:language>
    <dc:date>2020-12-12T11:23:44+09:00</dc:date>
    <items>
    <rdf:Seq>
      <rdf:li rdf:resource="http://tk0xleader.blog.shinobi.jp/boost/boost--operators--equality" />
      <rdf:li rdf:resource="http://tk0xleader.blog.shinobi.jp/c--/std--ranges-1-" />
      <rdf:li rdf:resource="http://tk0xleader.blog.shinobi.jp/c--/%E3%80%90c--2a%E3%80%91std--is_nothrow_con" />
      <rdf:li rdf:resource="http://tk0xleader.blog.shinobi.jp/c--/%E3%80%90c--2a%E3%80%91customization%20point%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6" />
    </rdf:Seq>
    </items>
  </channel>

  <item rdf:about="http://tk0xleader.blog.shinobi.jp/boost/boost--operators--equality">
    <link>http://tk0xleader.blog.shinobi.jp/boost/boost--operators--equality</link>
    <title>boost::operators::equality_comparable2とC++20の比較演算子の問題</title>
    <description>C++20で比較演算子のオーバーロード解決のルールが大幅に変更になりました。
その影響で、boost::operators::equality_comparable2が特定のケースでコンパイルエラーが生じる結果となることが報告されています*1。

問題のコードは大体こんな感じ。


#include...</description>
    <content:encoded><![CDATA[C++20で比較演算子のオーバーロード解決のルールが大幅に変更になりました。<br />
その影響で、boost::operators::equality_comparable2が特定のケースでコンパイルエラーが生じる結果となることが報告されています<a href="#note1">*1</a>。<br />
<br />
問題のコードは大体こんな感じ。<br />
<br />

<pre>#include &lt;boost/operators.hpp&gt;

struct my_type : boost::equality_comparable2&lt;my_type, double&gt; {
    explicit my_type(double mem) : mem_{mem}{}
    double mem_{0};
    operator double() {
        return mem_;
    }
    bool operator==(my_type l) {
        return l.mem_ == mem_;
    }
}; 


int main() {
    my_type x{0};
    return x == double{0};
}
</pre>
(下記issueから引用) <br />
どういうことかというと、C++20では、「x==y」という式に対して、「y==x」と式を書き換えた場合を加えてオーバーロード解決をしようとします。 すると、x==double{0}; という式に対して、double{0}==xという式に書き換えた場合の等価演算子も候補に入るわけですね。<br />
ところで、boost::equality_comparable2&lt;X, Y&gt;は、 bool operator==(const Y&amp; y, const X&amp; x){ return x==y;} という形で、operator==(X, Y)のみ用意しておけば、自動的にoperator==(Y, X)も実装されるということです。 <br />
<br />
じゃあ、上のケースで、x==double{0}という式はどうなるか。C++17までであれば、mytypeからdoubleへの暗黙変換が可能なので、double同士の組み込みの比較演算子のみがマッチするため、それが選ばれるわけです。 <br />
<br />
ところが、C++20だと、x==double{0};が、double{0}==x;と書き換えられた式についてもオーバーロード解決にに加わるため、equality_comparable2内で定義されるfriend関数である、operator(const double&amp;, const mytype&amp;);がマッチしてしまうんですよね。しかも、double同士の組み込みの演算子と違い、変換なしでぴったり一致してしまうので、こちらが選ばれてしまうわけです。 <br />
すると、operator(const double&amp;, const mytype&amp;)内で、再びmytype == doubleの比較をしてしまうため、無限再帰をしてしまうわけです。<br />
<br />
<span id="note1">*1</span> https://github.com/boostorg/utility/issues/65]]></content:encoded>
    <dc:subject>Boost</dc:subject>
    <dc:date>2020-12-12T11:23:44+09:00</dc:date>
    <dc:creator>tk-xleader</dc:creator>
    <dc:publisher>NINJA BLOG</dc:publisher>
    <dc:rights>tk-xleader</dc:rights>
  </item>
  <item rdf:about="http://tk0xleader.blog.shinobi.jp/c--/std--ranges-1-">
    <link>http://tk0xleader.blog.shinobi.jp/c--/std--ranges-1-</link>
    <title>【C++2a】std::ranges::begin, std::ranges::endの実装例</title>
    <description>C++2aで導入されるRangesライブラリでは、std::ranges::beginによってIteratorを、std::ranges::endによってSentinel*1を取り出すことができる型をRangeとして扱うことになっています。
そのstd::ranges::beginとstd::ran...</description>
    <content:encoded><![CDATA[C++2aで導入される<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0896r4.pdf" title="">Rangesライブラリ</a>では、std::ranges::beginによってIteratorを、std::ranges::endによってSentinel<sup><a href="#note1">*1</a></sup>を取り出すことができる型をRangeとして扱うことになっています。<br />
そのstd::ranges::beginとstd::ranges::endですが、次のようなCustomization Point Objectになっています。どちらも、有効な呼び出しができないときはSFINAE-friendly error（実体化の失敗）になります。<br />
<br />
式rに対して、std::ranges::begin(r) は、次の順に有効性をテストして、有効なものをdecay_copyしたものを意味します。<br />
<ol>
<li>rが配列のlvalueのときは、r + 0; // つまり、配列の最初の要素へのポインタ</li>
<li>rがlvalueで、かつr.begin()がstd::ranges::iteratorコンセプトを満たす型を返す式として有効であるときは、 r.begin();</li>
<li>オーバーロード集合として、template&lt;typename T&gt; void begin(T&amp;&amp;) = delete; template&lt;typename T&gt; void begin(std::initializer_list&lt;T&gt;&amp;&amp;) = delete; を加えたうえで、begin(static_cast&lt;decltype(r)&amp;&amp;&gt;(r))がstd::ranges::iteratorコンセプトを満たす型を返す式として有効であるときは、begin(static_cast&lt;decltype(r)&amp;&amp;&gt;(r));</li>
</ol>そして、式rに対して、std::ranges::end(r)は、次の順に有効性をテストして、有効なものをdecay_copyしたものを意味します。<br />
<ol>
<li>rが配列のlvalueのときは、r + std::extend_v&lt;decltype(r)&gt;;</li>
<li>rがlvalueで、r.endがstd::ranges::begin(r)の戻り値型に対するstd::ranges::sentinelコンセプトを満たす型を返す式として有効であるときは、r.end();</li>
<li>オーバーロード集合として、template&lt;typename T&gt; void end(T&amp;&amp;) = delete; template&lt;typename T&gt; void end(std::initializer_list&lt;T&gt;&amp;&amp;) = delete; を加えたうえで、end(static_cast&lt;decltype(r)&amp;&amp;&gt;(r))がstd::ranges::begin(r)の戻り値型に対するstd::ranges::sentinelコンセプトを満たす型を返す式として有効であるときは、end(static_cast&lt;decltype(r)&amp;&amp;&gt;(r));</li>
</ol>rvalueに対してはフリー関数のbegin/endに限定しているのは、rvalueは寿命がすぐに尽きる値を意味する<sup><a href="#note2">*2</a></sup>ので、そこから取り出したIterator/Sentinelが無効領域を指すものとなってしまう危険があるからです。<br />
rvalueに対して有効にIterator/Sentinelを呼び出したい場合、ADLを通してbegin/endが呼び出せるようにします。標準では、実体の寿命を管理しないstd::string_viewやstd::spanは、friend関数としてbegin/endを定義しています。<br />
これらを踏まえて、std::ranges::begin/endを実装すると、こうなります。
<pre>namespace std::range{
  namespace __begin_impl_ns{
    template&lt;typename T&gt; void begin(T&amp;&amp;) = delete;
    template&lt;typename T&gt; void begin(std::initializer_list&lt;T&gt;&amp;&amp;) = delete;
    class _begin_functor{
      template&lt;typename E, std::size_t size&gt;
      static E* impl(E (&amp;arr)[size]){
        return arr;
      }<br />
     template&lt;typename E&gt;
      static E* impl(E (&amp;arr)[]){
        return arr;
      }
      template&lt;typename R&gt; requires iterator_type&lt;decltype((std::declval&lt;R&amp;&gt;()).begin())&gt;
      static auto impl(R&amp; r){
        return r.begin();
      }
      template&lt;typename R&gt; requires iterator_type&lt;decltype(begin(std::declval&lt;R&gt;()))&gt;
      static auto impl(R&amp;&amp; r){
        return begin(std::forward&lt;R&gt;(r));
      }
    public:
      template&lt;typename R&gt;
      auto operator()(R&amp;&amp; r)-&gt;decltype(impl(std::forward&lt;R&gt;(r)))const{
        return impl(std::forward&lt;R&gt;(r));
      }
    };
  }
  inline constexpr __begin_impl_ns::_begin_functor begin{};
  
  namespace __end_impl_ns{
    template&lt;typename T&gt; void end(T&amp;&amp;) = delete;
    template&lt;typename T&gt; void end(std::initializer_list&lt;T&gt;&amp;&amp;) = delete;
    class _end_functor{
      template&lt;typename E, std::size_t size&gt;
      static E* impl(E (&amp;arr)[size]){
        return arr + size;
      }<br />
     template&lt;typename E&gt;
      static E* impl(E(&amp;arr)[]){
        return arr;
      }
      template&lt;typename R&gt;
      requires sentinel_for&lt;decltype((std::declval&lt;R&amp;&gt;()).end()),decltype(ranges::begin(std::declval&lt;R&amp;&gt;()))&gt;
      static auto impl(R&amp; r){
        return r.end();
      }
      template&lt;typename R&gt;
      requires sentinel_for&lt;decltype(end(std::declval&lt;R&gt;())),decltype(ranges::begin(std::declval&lt;R&gt;()))&gt;
      static auto impl(R&amp;&amp; r){
        return end(std::forward&lt;R&gt;(r));
      }
    public:
      template&lt;typename R&gt;
      auto operator()(R&amp;&amp; r)-&gt;decltype(impl(std::forward&lt;R&gt;(r)))const{
        return impl(std::forward&lt;R&gt;(r));
      }
    };
  }
  inline constexpr __end_impl_ns::_end_functor end{};
}</pre>
<ol>
<li id="note1">直訳すると「番兵」、Rangesライブラリでは、Iteratorと比較演算子で比較でき、要素の最後の一つ後ろのIteratorと等価のものとして扱われる。Iteratorと同じ型であってもよい。</li>
<li>アルゴリズム関数は、RangeをR&amp;&amp;で受け取ってそのまま（perfect forwardingせずに）std::ranges::begin/endに渡すので、アルゴリズム関数にはrvalueを渡すことができます。</li>
</ol>]]></content:encoded>
    <dc:subject>C++</dc:subject>
    <dc:date>2019-08-04T17:05:09+09:00</dc:date>
    <dc:creator>tk-xleader</dc:creator>
    <dc:publisher>NINJA BLOG</dc:publisher>
    <dc:rights>tk-xleader</dc:rights>
  </item>
  <item rdf:about="http://tk0xleader.blog.shinobi.jp/c--/%E3%80%90c--2a%E3%80%91std--is_nothrow_con">
    <link>http://tk0xleader.blog.shinobi.jp/c--/%E3%80%90c--2a%E3%80%91std--is_nothrow_con</link>
    <title>【C++2a】std::is_nothrow_convertibleのC++17での実装例</title>
    <description>std::is_nothrow_convertibleは、C++2aで&amp;amp;lt;type_traits&amp;amp;gt;ヘッダに追加されるメタ関数で、型Fromから型Toに暗黙変換が可能であり、例外を投げないことが保証されているかどうかを調べるものです。

TからUに暗黙変換が可能というのは、

U func(...</description>
    <content:encoded><![CDATA[std::is_nothrow_convertibleは、C++2aで&lt;type_traits&gt;ヘッダに追加されるメタ関数で、型Fromから型Toに暗黙変換が可能であり、例外を投げないことが保証されているかどうかを調べるものです。<br />
<br />
TからUに暗黙変換が可能というのは、<br />

<pre>U func(){ return std::declval&lt;T&gt;();}
</pre>
という関数が有効ということを意味しますが、SFINAEでは、戻り値の有効性はチェックできませんから、これを何とか引数に持ってきます。<br />
その結果、C++17では、is_nothrow_convertibleは<a href="https://gist.github.com/tk-xleader/f84b6f81bb8988962b3651b420dd3651" title="">このような実装</a>になります。<br />
<br />

<pre>namespace std{
	namespace _details{
		template&lt;typename To&gt;
		std::void_t&lt;To()&gt; convert_to(To) noexcept;
		
		template&lt;typename From, typename To, typename = void&gt;
		struct is_nothrow_convertible_imp : std::false_type{};

		template&lt;typename From, typename To&gt;
		struct is_nothrow_convertible_imp&lt;From, To, typename std::enable_if&lt;noexcept(std::_details::convert_to&lt;To&gt;(std::declval&lt;From&gt;()))&gt;::type&gt; : std::true_type{};
	}

	template&lt;typename From, typename To&gt;
	struct is_nothrow_convertible : std::conditional&lt;
		std::is_void&lt;From&gt;::value,
		std::is_void&lt;To&gt;,
		_details::is_nothrow_convertible_imp&lt;From, To&gt;
	&gt;::type{};
}
</pre>
関数の引数と戻り値のどちらでも暗黙変換が起こりうる文脈ですが、
<ul>
<li>関数の仮引数の型が、配列型・関数型となっている場合、decayが発生するが、戻り値型の場合はこれが起こらずエラーになる。</li>
<li>関数の戻り値型がvoidの場合にvoid型を返す関数の呼び出しをreturn文の式に記述できるが、仮引数型がvoidの場合はこれができずエラーになる。</li>
</ul>
という点で違いますから、次のようにすることで、return文で判定したときと一致するようにさせます。
<ul>
<li>Fromがvoidの場合、Toがvoidかどうかを判定する。</li>
<li>convert_toヘルパ関数の戻り値をstd::void_t&lt;To()&gt;とすることで、Toが配列型・関数型の場合にconvert_toヘルパ関数が不正なシグネチャとなるように細工する。</li>
</ul>
detection idiomの変則系として、std::void_tではなくて、std::enable_ifを使うもできます。]]></content:encoded>
    <dc:subject>C++</dc:subject>
    <dc:date>2019-08-04T15:36:56+09:00</dc:date>
    <dc:creator>tk-xleader</dc:creator>
    <dc:publisher>NINJA BLOG</dc:publisher>
    <dc:rights>tk-xleader</dc:rights>
  </item>
  <item rdf:about="http://tk0xleader.blog.shinobi.jp/c--/%E3%80%90c--2a%E3%80%91customization%20point%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">
    <link>http://tk0xleader.blog.shinobi.jp/c--/%E3%80%90c--2a%E3%80%91customization%20point%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6</link>
    <title>【C++2a】Customization pointについて</title>
    <description>C++2a規格では、標準ライブラリに関してCustomization pointという概念が導入されます。Customization pointとは、標準ライブラリの関数テンプレートの中で、ユーザー定義の型に対する特殊な定義を付け加えることを前提としたもののことです。C++2aでは、標準ライブラリの...</description>
    <content:encoded><![CDATA[C++2a規格では、標準ライブラリに関してCustomization pointという概念が導入されます。Customization pointとは、標準ライブラリの関数テンプレートの中で、ユーザー定義の型に対する特殊な定義を付け加えることを前提としたもののことです。C++2aでは、標準ライブラリの関数テンプレートについて、カスタム定義をすることができるものを限定し、その上で、カスタム定義の方法についても、ADLを前提としたものに限るというルールに変更されます。<br />
<br />
<span style="text-decoration: underline;"><strong>変更点のポイント</strong></span><br />
<br />
C++17（<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf" title="">N4659</a>）までは、std名前空間内のテンプレートに対して、一定の場合を除いて完全特殊化／部分特殊化を定義することができると決められています。関数テンプレートの場合、部分特殊化を定義することができない<sup>1</sup>ため、関数テンプレートについては、完全特殊化の場合のみが許されていることになります。<br />
これが、C++2a（<a href="https://github.com/cplusplus/draft/raw/master/papers/n4762.pdf" title="">n4762</a>）では、ユーザー定義型に対する特殊化を定義することができるのはクラステンプレートのみとするように変更されます。つまり、std名前空間内の関数テンプレートについては、完全特殊化も含めて特殊化をすることが禁止されます。この変更の理由は、"<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0551r3.pdf" title="">Thou Shalt Not Specialize std Function Templates!</a>"（提案文書p0551r3）によれば、関数テンプレートの特殊化はオーバーロード解決に際して考慮されないため、関数テンプレートの特殊化はユーザーの意図とは異なる結果をもたらす可能性があります。例えば、次のようなコードがあるとします。<br />

<pre><code>/*code start*/<br />





/*in library code.*/
#include&lt;iostream&gt;

namespace library{
	template&lt;typename T&gt;
	void func(const T&amp;){}
	
	template&lt;typename T&gt;
	struct X{};
	
	template&lt;typename T&gt;
	void func(const X&lt;T&gt;&amp;){
		std::cout &lt;&lt; "template&lt;typename T&gt; void func(const X&lt;T&gt;&amp;);" &lt;&lt; std::endl;
	}// ※1
}

/*user code*/

namespace user{
	class Y{};
}
namespace library{
	template&lt;&gt;
	void func&lt;X&lt;user::Y&gt;&gt;(const X&lt;user::Y&gt;&amp;){
		std::cout &lt;&lt; "template&lt;&gt; void func&lt;X&lt;Y&gt;&gt;(const X&lt;Y&gt;&amp;);" &lt;&lt;std::endl;
	}// ※2
}

int main(){
	using namespace library;
	func(library::X&lt;user::Y&gt;{});//出力は？
}<br />





//end<br />





</code></pre>
一般的に、ユーザーは※2が呼ばれることを期待するでしょう。ところが、このコードで呼ばれるfuncは、※2ではなく※1となります。こういう問題があるため、std名前空間内の関数テンプレートの特殊化は禁止すべきだということになったわけです。結果として、Customization pointのカスタム化は、ユーザー定義型と同じ名前空間内に同名の関数を定義して、標準ライブラリはそれを非修飾名で呼び出してADLで解決させるという方法に落ち着くということになります。<br />
C++2aでは、std::swap, std::begin, std::endなどがCustomization pointとして指定されることになっています。<br />
≪追記≫<br />
※2を※1の特殊化として実装する場合、次のように定義するのが正解です。
<pre><code>
namespace library{
	template&lt;&gt;
	void func&lt;&gt;(const X&lt;user::Y&gt;&amp;){
		std::cout &lt;&lt; "template&lt;&gt; void func&lt;X&lt;Y&gt;&gt;(const X&lt;Y&gt;&amp;);" &lt;&lt;std::endl;
	}
}
</code></pre>
<br />
<span style="text-decoration: underline;"><strong>Customization point object</strong></span><br />
<br />
さらに、Customization point "object"という概念も導入されています<sup>2</sup>。 これは、Customization pointをユーザーコードから呼び出す場合に、usingを使わなくてもユーザー定義のCustomization pointが呼び出されるようにするための機構です。実現方法は異なります<sup>3</sup>が、boost::swapと同様の目的があります。<br />
Customization point objectは、その名の通りオブジェクトとして実装されます。"<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html" title="">Suggested Design for Customization Points</a>"（N4381）と提唱者の<a href="http://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/" title="">Eric&nbsp;Niebler氏のブログエントリ</a>で詳細が述べられていますが、仮に、std::swapをCustomization point objectとして実装した場合、次のような実装になります。<br />

<pre><code>//start code
namespace std{
	namespace __swap_details{
		template&lt;typename T&gt;
		inline typename std::enable_if&lt;std::is_move_constructible&lt;T&gt;::value&amp;&amp;std::is_move_assignable&lt;T&gt;::value&gt;::type 
		swap(T&amp; obj1, T&amp; obj2)noexcept(std::is_nothrow_move_constructible&lt;T&gt;::value&amp;&amp;std::is_nothrow_move_assignable&lt;T&gt;::value){
			T temp(std::move(obj1));
			obj1 = std::move(obj2);
			obj2 = std::move(temp);
		} //※1
		struct _swap_functor{
			template&lt;typename T, typename U&gt;
			auto operator()(T&amp; obj1, U&amp; obj2)const noexcept(noexcept(swap(obj1, obj2)))-&gt;decltype(static_cast&lt;void&gt;(swap(obj1, obj2))){
				swap(obj1, obj2);
			} //※2
		};
	}
	inline constexpr __swap_details::_swap_functor swap{}; //※3<br />





<br />





	/*
	// Eric Niebler氏のエントリでは、ODR違反の回避のため※3は次のような実装になっている。
	// C++17にはinline変数が導入されたので、変数swapはinline変数として定義できる。<br />






	template&lt;typename T&gt;
	struct __static_valuable{
		static constexpr T value;
	};
	template&lt;typename T&gt;
	constexpr T __static_valuable&lt;T&gt;::value;
	
	namespace{
		constexpr auto const&amp; swap = __static_valuable&lt;__swap_details::_swap_functor&gt;::value;
	}
	*/
}
//end code</code></pre>
まず、※1のように、swap関数のデフォルトの実装を用意します。肝は、std名前空間とは別の名前空間にこれを定義することです。そして、これと同じ名前空間内に、swap関数を非修飾名で呼び出すような関数オブジェクト_swap_functorを定義します。_swap_functorは、ユーザー定義のswapをADLによって呼び出すことができます。最後に、std名前空間にswapという名前で_swap_functor型の変数を定義します。こうすることで、std::swapとして関数テンプレートと同様のSyntaxで呼び出すことができます。<br />
<br />
このように定義したswapは、std::swapと修飾名で呼び出した場合であっても、ADLによってユーザー定義のswapが呼び出されます。そのため、using std::swap; や、using namespace std; としてswapを非修飾名で呼び出す方法（探索先にstd名前空間を加えてADLを意図的に引き起こす手段）でなくても、それと同等の効果が得られるというわけです。<br />
ただし、この方法を取った場合、ユーザーがデフォルトのswap関数に限定して呼び出す方法がなくなります。もちろん、それはライブラリ実装者の意図するところではあるのですが。<br />
<br />
<span style="text-decoration: underline;"><strong>ユーザーコードへの応用</strong></span><br />
<br />
仮に、boost::swapをCustomization point objectと同様の手法を利用して実装すると、次のような実装になります。<br />

<pre><code>//start code<br />





namespace boost{
	namespace _swap_impl{
		using std::swap;
		struct _swap_functor{
			template&lt;typename T, typename U&gt;
			void operator(T&amp; obj1, U&amp; obj2)const noexcept(noexcept(swap(obj1, obj2))){
				swap(obj1, obj2);
			}
		};
	}
	inline constexpr _swap_impl::_swap_functor swap{}; //since C++17.
}
//end code</code></pre>
std::swapを定義する場合と異なるのは、デフォルトの実装として用意されたswap関数が、修飾名でstd::swapを呼び出すようにしたことです。もう一つ、SFINAEによる限定を一切行っていないことです。その理由としては、標準規格上、swap関数テンプレートは、引数の型がswap関数のテンプレート引数の要件を満たさない場合（つまり、ムーブ構築とムーブ代入のどちらか出来ない型である場合）はオーバーロード解決から外すことが要求されますが、boost::swapについては、独自のswapが定義されておらず、かつstd::swapが呼び出せない型についてはエラーにしてしまえばいいので、一々面倒なenable_ifを書く必要はないだろう思ったからです。<br />
<br />
<strong><span style="text-decoration: underline;">Appendix: ADLとtemplateライブラリ</span></strong><br />
<br />
ここまでの話は、ADLというC++特有の名前探索の機構を前提とした話です。そして、残念なことに多くのC++erはADLについてあまり好意的ではありません。なぜなら、ADLでは予測しなかった関数が呼ばれる可能性があるからです。C++erは、ADLに対して十分に注意を払う必要があります。ここでは、templateライブラリを作成するときと、それを利用するときについて検討することにしましょう。<br />
templateライブラリを作成する側は、テンプレート内で関数を呼び出す場合に、呼び出す関数がライブラリユーザーによるカスタマイズを前提とした関数なのか、そうでないのかを分ける必要があります。前者の場合は非修飾名で呼び出しても（あるいは、Customization point objectとしてしまっても）問題はないと思います。呼び出す関数が後者の場合、ADLが発生しないような方法で呼び出すようにしましょう。ADLを発生させないためには、関数を修飾名で呼び出すという方法があります。<br />
また、カスタマイズを前提とする関数を呼び出す場合、同一名前空間内に、引数の型が取りうる、最も一般的なテンプレート仮引数を取る関数テンプレートをデフォルトの実装として定義するべきだろうと思います。例えば、<br />

<pre><code>//start code
namespace library{
	template&lt;typename T, typename U, typename V&gt;
	void func(const T&amp; t, U* u, const std::vector&lt;V&gt;&amp; v){
		inner_func1(t);
		inner_func2(u);
		inner_func3(v);
	}
}
//end code
</code></pre>
という関数テンプレートを書くとしたら、
<pre><code>//start code<br />





namespace library{
	template&lt;typename T&gt;
	void inner_func1(const T&amp; t){/*...*/}
	
	template&lt;typename T&gt;
	void inner_func2(T* t){/*...*/}
	
	template&lt;typename T&gt;
	void inner_func3(const std::vector&lt;T&gt;&amp; t){/*...*/}
}
//end code</code></pre>
という関数を定義するべきだということです。その理由は、カスタマイズされた関数は、当然のことながらテンプレート引数よりも特殊な型になっているはずなので、デフォルトの実装のような一般的に過ぎる関数テンプレートであるべきではないからです。仮に、全ての型についてカスタマイズすることを要求する（つまり、デフォルトの実装を用意しない）場合、delete定義としておけば事足ります。<br />
<br />
ライブラリのユーザーは、ユーザー定義型を定義するときに、不用意にADLが発生しないようにしましょう（もちろん、ADLで呼ばれることを意図した関数については別です）。ADLを妨害する方法としては、<a href="http://cpp.aquariuscode.com/adl-firewall" title="">ADL firewall</a>という手法があります。その肝はユーザー定義型とフリー関数を異なる名前空間で定義することです。一般的には、ネストした名前空間を用意して、その中でユーザー定義型を定義して、関数を定義した名前空間でusing宣言、あるいはtypedef宣言するという方法を取ります。<br />

<pre><code>//start code
namespace ns{
	namespace nested{
		struct X{};
	}
	
	// 次のどれでもよい。どの方法でも、ns::Xでns::nested::Xを指すことができる。
	using nested::X;    //(1)
	typedef nested::X X;//(2)
	using namespace X;  //(3)
	
	void func(X x){}
}
//end code</code></pre>
一方、関数の方をネストされた名前空間に入れてもよいですが、この場合はusing宣言（1の方法）ではだめです。なぜなら、using宣言によって宣言された関数は、ADLの対象になるからです<sup>4</sup>。<br />
<hr /><ol>
<li>関数テンプレートについて、template&lt;typename T&gt; void swap&lt;foo&lt;T&gt;&gt;(foo&lt;T&gt;&amp;, foo&lt;T&gt;&amp;){/*...*/} みたいな定義をすることはできない。template&lt;typename T&gt; void swap(foo&lt;T&gt;&amp;, foo&lt;T&gt;&amp;){/*...*/} とした場合、これは部分特殊化ではなくて関数テンプレートのオーバーロードとなる。</li>
<li>現在の最新draft（N4659）の段階では、Customization point objectに指定されたものは見当たらない。これは、N4381で検討されているが、既存のCustomization pointにこれを適用すると、Customization pointを修飾名で呼び出しているコードを破壊的に変更することになるからである（Customization pointを修飾名で呼び出している場合、ユーザー定義の関数は呼ばれず、std名前空間内の関数が呼ばれる）。</li>
<li>N4659では、Customization point objectの型はliteral class typeでなければならないとされているため。一方で、boost::swapを関数オブジェクトにしてしまった場合、boost名前空間にswap関数を定義することができなくなってしまうため、boost::swapは関数として定義せざるを得ない。ところで、boost::swap自身は、std::swapを非修飾名で呼び出せる文脈においては、boost名前空間内で定義されている型に対してADLによって呼ばれることはない。なぜなら、boost::swapは、テンプレート引数を2つ取り、テンプレート引数が1つであるstd::swapよりもオーバーロード解決について劣後するためである。</li>
<li>これを利用すれば、<a href="https://qiita.com/tk-xleader/items/792555e2502e2cb62ad5" title="">他の名前空間で定義された関数をADLの対象にすることができる</a>。例えば、std::rel_ops名前空間内の演算子関数については、本来ならばこの方法で使うべきではないかと思われる（もっとも、std::rel_ops名前空間内の比較演算子については、C++2aでdeprecatedとなることが決まっている）。</li>
<ol></ol></ol>]]></content:encoded>
    <dc:subject>C++</dc:subject>
    <dc:date>2018-10-04T23:42:10+09:00</dc:date>
    <dc:creator>tk-xleader</dc:creator>
    <dc:publisher>NINJA BLOG</dc:publisher>
    <dc:rights>tk-xleader</dc:rights>
  </item>
</rdf:RDF>
