tk-xleaderのブログ。C++などプログラミングの話題が中心です。
[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。
/*code start*/
/*in library code.*/
#include<iostream>
namespace library{
template<typename T>
void func(const T&){}
template<typename T>
struct X{};
template<typename T>
void func(const X<T>&){
std::cout << "template<typename T> void func(const X<T>&);" << std::endl;
}// ※1
}
/*user code*/
namespace user{
class Y{};
}
namespace library{
template<>
void func<X<user::Y>>(const X<user::Y>&){
std::cout << "template<> void func<X<Y>>(const X<Y>&);" <<std::endl;
}// ※2
}
int main(){
using namespace library;
func(library::X<user::Y>{});//出力は?
}
//end
一般的に、ユーザーは※2が呼ばれることを期待するでしょう。ところが、このコードで呼ばれるfuncは、※2ではなく※1となります。こういう問題があるため、std名前空間内の関数テンプレートの特殊化は禁止すべきだということになったわけです。結果として、Customization pointのカスタム化は、ユーザー定義型と同じ名前空間内に同名の関数を定義して、標準ライブラリはそれを非修飾名で呼び出してADLで解決させるという方法に落ち着くということになります。
namespace library{
template<>
void func<>(const X<user::Y>&){
std::cout << "template<> void func<X<Y>>(const X<Y>&);" <<std::endl;
}
}
//start code
namespace std{
namespace __swap_details{
template<typename T>
inline typename std::enable_if<std::is_move_constructible<T>::value&&std::is_move_assignable<T>::value>::type
swap(T& obj1, T& obj2)noexcept(std::is_nothrow_move_constructible<T>::value&&std::is_nothrow_move_assignable<T>::value){
T temp(std::move(obj1));
obj1 = std::move(obj2);
obj2 = std::move(temp);
} //※1
struct _swap_functor{
template<typename T, typename U>
auto operator()(T& obj1, U& obj2)const noexcept(noexcept(swap(obj1, obj2)))->decltype(static_cast<void>(swap(obj1, obj2))){
swap(obj1, obj2);
} //※2
};
}
inline constexpr __swap_details::_swap_functor swap{}; //※3
/*
// Eric Niebler氏のエントリでは、ODR違反の回避のため※3は次のような実装になっている。
// C++17にはinline変数が導入されたので、変数swapはinline変数として定義できる。
template<typename T>
struct __static_valuable{
static constexpr T value;
};
template<typename T>
constexpr T __static_valuable<T>::value;
namespace{
constexpr auto const& swap = __static_valuable<__swap_details::_swap_functor>::value;
}
*/
}
//end code
まず、※1のように、swap関数のデフォルトの実装を用意します。肝は、std名前空間とは別の名前空間にこれを定義することです。そして、これと同じ名前空間内に、swap関数を非修飾名で呼び出すような関数オブジェクト_swap_functorを定義します。_swap_functorは、ユーザー定義のswapをADLによって呼び出すことができます。最後に、std名前空間にswapという名前で_swap_functor型の変数を定義します。こうすることで、std::swapとして関数テンプレートと同様のSyntaxで呼び出すことができます。//start code
namespace boost{
namespace _swap_impl{
using std::swap;
struct _swap_functor{
template<typename T, typename U>
void operator(T& obj1, U& obj2)const noexcept(noexcept(swap(obj1, obj2))){
swap(obj1, obj2);
}
};
}
inline constexpr _swap_impl::_swap_functor swap{}; //since C++17.
}
//end code
std::swapを定義する場合と異なるのは、デフォルトの実装として用意されたswap関数が、修飾名でstd::swapを呼び出すようにしたことです。もう一つ、SFINAEによる限定を一切行っていないことです。その理由としては、標準規格上、swap関数テンプレートは、引数の型がswap関数のテンプレート引数の要件を満たさない場合(つまり、ムーブ構築とムーブ代入のどちらか出来ない型である場合)はオーバーロード解決から外すことが要求されますが、boost::swapについては、独自のswapが定義されておらず、かつstd::swapが呼び出せない型についてはエラーにしてしまえばいいので、一々面倒なenable_ifを書く必要はないだろう思ったからです。//start code
namespace library{
template<typename T, typename U, typename V>
void func(const T& t, U* u, const std::vector<V>& v){
inner_func1(t);
inner_func2(u);
inner_func3(v);
}
}
//end code
という関数テンプレートを書くとしたら、
//start code
namespace library{
template<typename T>
void inner_func1(const T& t){/*...*/}
template<typename T>
void inner_func2(T* t){/*...*/}
template<typename T>
void inner_func3(const std::vector<T>& t){/*...*/}
}
//end code
という関数を定義するべきだということです。その理由は、カスタマイズされた関数は、当然のことながらテンプレート引数よりも特殊な型になっているはずなので、デフォルトの実装のような一般的に過ぎる関数テンプレートであるべきではないからです。仮に、全ての型についてカスタマイズすることを要求する(つまり、デフォルトの実装を用意しない)場合、delete定義としておけば事足ります。//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
一方、関数の方をネストされた名前空間に入れてもよいですが、この場合はusing宣言(1の方法)ではだめです。なぜなら、using宣言によって宣言された関数は、ADLの対象になるからです4。