struct
標準が単純な boolean ではなくtemplate として指定する理由はありますconstexpr
か?
enable_if
主な質問に対する適切な回答でおそらく回答される追加の質問では、非構造体バージョンをどのように処理しますか?
struct
標準が単純な boolean ではなくtemplate として指定する理由はありますconstexpr
か?
enable_if
主な質問に対する適切な回答でおそらく回答される追加の質問では、非構造体バージョンをどのように処理しますか?
1つの理由は、constexpr
関数がネストされたメンバーを提供できないことtype
です。これは、一部のメタプログラミングの状況で役立ちます。
明確にするために、私は型を生成し、明らかに関数にすることができない変換特性(のような)だけについて話しているのではありません。すべての型特性は、単項型特性やバイナリ型特性でさえ、そのようなネストされたメンバーを提供します。たとえば、です。make_unsigned
constexpr
type
is_void<int>::type
false_type
もちろん、これはで回避できますがstd::integral_constant<bool, the_constexpr_function_version_of_some_trait<T>()>
、それほど実用的ではありません。
いずれにせよ、本当に関数のような構文が必要な場合は、それはすでに可能です。トレイトコンストラクターを使用して、constexprの暗黙的な変換をintegral_constant
利用できます。
static_assert(std::is_void<void>(), "void is void; who would have thunk?");
変換特性の場合、テンプレートエイリアスを使用して、その構文に近いものを取得できます。
template <bool Condition, typename T = void>
using enable_if = typename std::enable_if<Condition, T>::type;
// usage:
// template <typename T> enable_if<is_void<T>(), int> f();
//
// make_unsigned<T> x;
注:これは、適切な回答というよりも暴言のように見えます...ただし、以前の回答を読んで少しかゆくなったので、すみません;)
まず、クラスの特徴は歴史的にテンプレート構造で行われてきましconstexpr
たdecltype
。これら 2 つがなければ、関数を使用するのはもう少し面倒でしたが、さまざまなライブラリの実装でis_base_of
は、継承を正しく行うために内部で関数を使用する必要がありました。
関数を使用する利点は何ですか?
typename ::type
愚かな TMに見えます)実際、継承はおそらくクラスの特性に対する主なポイントです。mommaのようにすべての派生クラスを特殊化する必要があるのは、非常に面倒です。とてもうるさい。関数を使用すると、特性を継承するだけで、必要に応じて特殊化できます。
短所は何ですか?
もちろん、これは実際には煩わしいと主張する人もいるかもしれませiterator_traits
ん。さまざまな機能がこれを自然に提供します。std::iterator_traits
それはうまくいくでしょうか?
constexpr
まあ、 from を除いてすべてが基づいているという言葉でenable_if
(しかし、それは特性ではありません)、あなたは行くでしょう:
template <typename T>
typename enable_if<std::is_integral(T()) and
std::is_signed(T())>::type
std::declval
注:未評価のコンテキスト (つまり、ほとんど) が必要なため、ここでsizeof
はdecltype
使用しませんでした。したがって、1 つの追加要件 (すぐには表示されません) は、それT
がデフォルトで構築可能であることです。
本当に必要な場合は、ハックがあります。
#define VALUE_OF(Type_) decltype(std::declval<T>())
template <typename T>
typename enable_if<std::is_integral(VALUE_OF(T)) and
std::is_signed(VALUE_OF(T))>::type
また、定数ではなく型が必要な場合はどうすればよいですか?
decltype(common_type(std::declval<T>(), std::declval<U>()))
問題もありません(はい、ここでは を使用していますdeclval
)。しかし...型を渡すことは何の関係もありませんconstexpr
; 関数は、興味のある値constexpr
を返す場合に役立ちます。複雑な型を返す関数はもちろん使用できますが、そうではなく、型の値を使用しません。constexpr
また、trais と type をチェーンする必要がある場合はどうすればよいでしょうか?
皮肉なことに、これは関数が輝く場所です:)
// class version
template <typename Container>
struct iterator { typedef typename Container::iterator type; };
template <typename Container>
struct iterator<Container const> {
typedef typename Container::const_iterator type;
};
template <typename Container>
struct pointer_type {
typedef typename iterator<Container>::type::pointer_type type;
};
template <typename Container>
typename pointer_type<Container>::type front(Container& c);
// Here, have a cookie and a glass of milk for reading so far, good boy!
// Don't worry, the worse is behind you.
// function version
template <typename Container>
auto front(Container& c) -> decltype(*begin(c));
何!詐欺師!特性が定義されていません!
うーん...実際、それがポイントです。ではdecltype
、かなりの数の特性が冗長になりました。
ドライ!
継承はうまくいきます!
基本的なクラス階層を見てみましょう:
struct Base {};
struct Derived: Base {};
struct Rederived: Derived {};
そして特性を定義します:
// class version
template <typename T>
struct some_trait: std::false_type {};
template <>
struct some_trait<Base>: std::true_type {};
template <>
struct some_trait<Derived>: some_trait<Base> {}; // to inherit behavior
template <>
struct some_trait<Rederived>: some_trait<Derived> {};
注:トレイト forDerived
は、直接宣言するのではなくtrue
、false
祖先から動作を取得することを意図しています。このように、祖先がスタンスを変更すると、階層全体が自動的に追従します。ほとんどの場合、基本機能は祖先によって提供されるため、その特性に従うことは理にかなっています。型特性についてはなおさらです。
// function version
constexpr bool some_trait(...) { return false; }
constexpr bool some_trait(Base const&) { return true; }
注: 省略記号の使用は意図的なものであり、これはキャッチオール オーバーロードです。テンプレート関数は、他のオーバーロードよりも適切に一致します (変換は必要ありません) が、省略記号は常に最悪の一致であり、他のオーバーロードが適していないものだけを選択することを保証します。
後者のアプローチがどれほど簡潔かを正確に言う必要はないと思いますか?混乱を取り除くだけでなくtemplate <>
、無料で継承も取得できます。
enable_if
そう実装できますか?
残念ながらそうは思いませんが、すでに述べたように、これは特性ではありません。そして、バージョンは型ではなく引数を使用するため、std
うまく機能します:)constexpr
bool
だからなぜ?
唯一の技術的な理由は、コードの大部分が、歴史的に型 ( std::numeric_limit
) として提供されていた多くの特性にすでに依存しているため、一貫性がそれを決定することです。
さらに、移行がboost::is_*
非常に簡単になります。
個人的には残念だと思います。しかし、私は平均的な企業よりも、自分が書いた既存のコードをレビューすることにおそらく熱心です。
1つの理由は、type_traitsプロポーザルがconstexprプロポーザルよりも古いことです。
もう1つは、必要に応じて、独自のタイプの特殊化を追加できることです。
主な理由は、それtype_traits
がすでに の一部でtr1
あったため、基本的にほぼ同じ形式で標準に組み込まれることが保証されていたため、constexpr
. その他の考えられる理由は次のとおりです。
remove_pointer
定義するため、この方法で表現する必要があります。値を定義する特性と型を定義する特性に異なるインターフェースを持つことは不必要に思えるtype
value
templated structs
関数は部分的に特殊化できますが、関数はできません。そのため、一部の特性の実装が容易になる可能性があります2 番目の質問について: enable_if
a を定義するようにtype
(false が渡された場合はそうではありません)、 a 内のネストされた typedefstruct
が実際に進むべき道です
おそらく、ブーストにはテンプレートで実装された type_traits のバージョンが既にあったためです。
そして、標準化委員会のメンバーがどれだけコピーを後押ししているかは誰もが知っています。