web-dev-qa-db-ja.com

デフォルトの失敗ケースで条件付きタイプを実装する良い方法はありますか?

条件付きタイプを実装するために、コードを短く、非常に読みやすくするため、非常に楽しんでいます 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_tstd::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が実際にブランチからの生成物である場合は機能しません。読みやすい短い解決策はありますか?

14
Stack Danny

これを解決するには、間接参照のレベルを追加します。これにより、最も外側の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>にネストされた型がないことは問題ではありません(使用しないため) 。

18
Jonathan Wakely

ちょうど楽しみのために_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>>;
_
6
max66