条件付きタイプを実装するために、コードを短く、非常に読みやすくするため、非常に楽しんでいます std::conditional_t
:
template<std::size_t N>
using bit_type =
std::conditional_t<N == std::size_t{ 8 }, std::uint8_t,
std::conditional_t<N == std::size_t{ 16 }, std::uint16_t,
std::conditional_t<N == std::size_t{ 32 }, std::uint32_t,
std::conditional_t<N == std::size_t{ 64 }, std::uint64_t, void>>>>;
それを使用すると、非常に直感的に機能します。
bit_type<8u> a; // == std::uint8_t
bit_type<16u> b; // == std::uint16_t
bit_type<32u> c; // == std::uint32_t
bit_type<64u> d; // == std::uint64_t
ただし、これは純粋な条件付きタイプであるため、デフォルトのタイプ-void
-が必要です。したがって、N
が他の値である場合、その型は次のようになります。
bit_type<500u> f; // == void
これはコンパイルされませんが、生成型はまだ有効です。
つまり、bit_type<500u>* f;
と言って、有効なプログラムを作成できます。
それで、条件付きタイプの失敗ケースに達したときにコンパイルを失敗させる良い方法はありますか?
すぐに1つのアイデアは、最後のstd::conditional_t
を std::enable_if_t
に置き換えることです。
template<std::size_t N>
using bit_type =
std::conditional_t<N == std::size_t{ 8 }, std::uint8_t,
std::conditional_t<N == std::size_t{ 16 }, std::uint16_t,
std::conditional_t<N == std::size_t{ 32 }, std::uint32_t,
std::enable_if_t< N == std::size_t{ 64 }, std::uint64_t>>>>;
これの問題は、テンプレートが常に完全に評価されることです。つまり、std::enable_if_t
は常に完全に評価され、N != std::size_t{ 64 }
の場合は失敗します。ああ。
これに対する私の現在の主な回避策は、構造体と3つのusing
宣言を導入するのがかなり不格好です。
template<std::size_t N>
struct bit_type {
private:
using vtype =
std::conditional_t<N == std::size_t{ 8 }, std::uint8_t,
std::conditional_t<N == std::size_t{ 16 }, std::uint16_t,
std::conditional_t<N == std::size_t{ 32 }, std::uint32_t,
std::conditional_t<N == std::size_t{ 64 }, std::uint64_t, void>>>>;
public:
using type = std::enable_if_t<!std::is_same_v<vtype, void>, vtype>;
};
template<std::size_t N>
using bit_type_t = bit_type<N>::type;
static_assert(std::is_same_v<bit_type_t<64u>, std::uint64_t>, "");
これは通常は機能しますが、非常に多くのものが追加されるため、私はそれが嫌いです。テンプレートの特殊化を使用することもできます。また、void
を特別な型として予約しているため、void
が実際にブランチからの生成物である場合は機能しません。読みやすい短い解決策はありますか?
これを解決するには、間接参照のレベルを追加します。これにより、最も外側のconditional_t
の結果は型ではなく、::type
を適用する必要があるメタ関数になります。次に、enable_if
ではなくenable_if_t
を使用して、実際に必要でない限り::type
にアクセスしないようにします。
template<typename T> struct identity { using type = T; };
template<std::size_t N>
using bit_type = typename
std::conditional_t<N == std::size_t{ 8 }, identity<std::uint8_t>,
std::conditional_t<N == std::size_t{ 16 }, identity<std::uint16_t>,
std::conditional_t<N == std::size_t{ 32 }, identity<std::uint32_t>,
std::enable_if<N == std::size_t{ 64 }, std::uint64_t>>>>::type;
このバージョンでは、最後のブランチのタイプはenable_if<
condition
, uint64_t>
であり、これは常に有効なタイプであり、そのブランチが実際に実行され、enable_if<false, uint64_t>::type
が必要な場合にのみエラーが発生します。 。以前の分岐のいずれかが実行されると、小さい整数型の1つにidentity<uintNN_t>::type
を使用することになり、enable_if<false, uint64_t>
にネストされた型がないことは問題ではありません(使用しないため) 。
ちょうど楽しみのために_std::Tuple
_と_std::Tuple_element
_を使用して_std::conditional
_をまったく避けてどうですか?
C++ 14(テンプレート変数とテンプレート変数の特殊化)を使用できる場合は、変換サイズ/インデックス内のタプルのテンプレート変数を記述できます。
_template <std::size_t>
constexpr std::size_t bt_index = 100u; // bad value
template <> constexpr std::size_t bt_index<8u> = 0u;
template <> constexpr std::size_t bt_index<16u> = 1u;
template <> constexpr std::size_t bt_index<32u> = 2u;
template <> constexpr std::size_t bt_index<64u> = 3u;
_
したがって、_bit_type
_は
_template <std::size_t N>
using bit_type = std::Tuple_element_t<bt_index<N>,
std::Tuple<std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t>>;
_
C++ 11のみを使用できる場合は、正しい(または正しくない)値を返すbt_index()
constexpr
関数を開発できます。
満足していることを確認できます
_static_assert( std::is_same_v<bit_type<8u>, std::uint8_t>, "!" );
static_assert( std::is_same_v<bit_type<16u>, std::uint16_t>, "!" );
static_assert( std::is_same_v<bit_type<32u>, std::uint32_t>, "!" );
static_assert( std::is_same_v<bit_type<64u>, std::uint64_t>, "!" );
_
サポートされていないディメンションで_bit_type
_を使用している
_bit_type<42u> * pbt42;
_
コンパイルエラーが発生します。
-EDIT- Jonathan Wakelyによって提案されているように、C++ 20を使用できる場合、std::ispow2()
とstd::log2p1()
を使用すると、大幅に簡略化できます。あなたは_bt_index
_をまったく避けて単純に書くことができます
_template <std::size_t N>
using bit_type = std::Tuple_element_t<std::ispow2(N) ? std::log2p1(N)-4u : -1,
std::Tuple<std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t>>;
_