tk-xleaderのブログ。C++などプログラミングの話題が中心です。
[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。
#include <boost/operators.hpp> struct my_type : boost::equality_comparable2<my_type, double> { 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}; }(下記issueから引用)
namespace std::range{ namespace __begin_impl_ns{ template<typename T> void begin(T&&) = delete; template<typename T> void begin(std::initializer_list<T>&&) = delete; class _begin_functor{ template<typename E, std::size_t size> static E* impl(E (&arr)[size]){ return arr; }
template<typename E> static E* impl(E (&arr)[]){ return arr; } template<typename R> requires iterator_type<decltype((std::declval<R&>()).begin())> static auto impl(R& r){ return r.begin(); } template<typename R> requires iterator_type<decltype(begin(std::declval<R>()))> static auto impl(R&& r){ return begin(std::forward<R>(r)); } public: template<typename R> auto operator()(R&& r)->decltype(impl(std::forward<R>(r)))const{ return impl(std::forward<R>(r)); } }; } inline constexpr __begin_impl_ns::_begin_functor begin{}; namespace __end_impl_ns{ template<typename T> void end(T&&) = delete; template<typename T> void end(std::initializer_list<T>&&) = delete; class _end_functor{ template<typename E, std::size_t size> static E* impl(E (&arr)[size]){ return arr + size; }
template<typename E> static E* impl(E(&arr)[]){ return arr; } template<typename R> requires sentinel_for<decltype((std::declval<R&>()).end()),decltype(ranges::begin(std::declval<R&>()))> static auto impl(R& r){ return r.end(); } template<typename R> requires sentinel_for<decltype(end(std::declval<R>())),decltype(ranges::begin(std::declval<R>()))> static auto impl(R&& r){ return end(std::forward<R>(r)); } public: template<typename R> auto operator()(R&& r)->decltype(impl(std::forward<R>(r)))const{ return impl(std::forward<R>(r)); } }; } inline constexpr __end_impl_ns::_end_functor end{}; }
U func(){ return std::declval<T>();}という関数が有効ということを意味しますが、SFINAEでは、戻り値の有効性はチェックできませんから、これを何とか引数に持ってきます。
namespace std{ namespace _details{ template<typename To> std::void_t<To()> convert_to(To) noexcept; template<typename From, typename To, typename = void> struct is_nothrow_convertible_imp : std::false_type{}; template<typename From, typename To> struct is_nothrow_convertible_imp<From, To, typename std::enable_if<noexcept(std::_details::convert_to<To>(std::declval<From>()))>::type> : std::true_type{}; } template<typename From, typename To> struct is_nothrow_convertible : std::conditional< std::is_void<From>::value, std::is_void<To>, _details::is_nothrow_convertible_imp<From, To> >::type{}; }関数の引数と戻り値のどちらでも暗黙変換が起こりうる文脈ですが、
/*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。