如何使用`std::conditional`以确保所有选项都有检查,而没有选项是默认的。

huangapple go评论65阅读模式
英文:

How to use std::conditional such that all options have a check / no option is the default

问题

std::conditional_t 可以无限嵌套:

#include <cstdint>
#include <cstring>
#include <type_traits>

enum class data_types { single_bytes, four_byte_integrals, four_byte_floats };

template<data_types expected_type>
std::conditional_t<expected_type == data_types::single_bytes, uint8_t,
std::conditional_t<expected_type == data_types::four_byte_integrals, int32_t,
                                                                     float>>
parse(const uint8_t * rawBytes)
{
    using return_t = std::conditional_t<expected_type == data_types::single_bytes, uint8_t,
                     std::conditional_t<expected_type == data_types::four_byte_integrals, int32_t,
                                                                                          float>>;
    constexpr auto length = sizeof(return_t);
    return_t result;
    std::memcpy(&result, rawBytes, length);
    return result;
}

然而,这会将最后一个选项作为默认情况,如果所有条件都失败,可能不太理想。

std::enable_if 存在的目的是防止模板实例化,如果条件不满足:

#include <cstdint>
#include <cstring>
#include <type_traits>

enum class data_types { single_bytes, four_byte_integrals, four_byte_floats };

template<data_types expected_type>
std::enable_if_t<expected_type == data_types::four_byte_floats , float>
parse(const uint8_t * rawBytes)
{
    float result;
    constexpr auto length = sizeof(float);
    std::memcpy(&result, rawBytes, length);
    return result;
}

但是,将 std::enable_if 嵌套到 std::conditional 中总是失败,即使先前的分支已足够(std::conditional 不会短路):

#include <cstdint>
#include <cstring>
#include <type_traits>

enum class data_types { single_bytes, four_byte_integrals, four_byte_floats };

template<data_types expected_type>
std::conditional_t<expected_type == data_types::single_bytes, uint8_t,
std::conditional_t<expected_type == data_types::four_byte_integrals, int32_t,
std::enable_if_t<expected_type == data_types::four_byte_floats, float>>>>
parse(const uint8_t * rawBytes)
{
    using return_t = std::conditional_t<expected_type == data_types::single_bytes, uint8_t,
                     std::conditional_t<expected_type == data_types::four_byte_integrals, int32_t,
                     std::enable_if_t<expected_type == data_types::four_byte_floats, float>>>;
    constexpr auto length = sizeof(return_t);
    return_t result;
    std::memcpy(&result, rawBytes, length);
    return result;
}

对于这个用法,会发出错误:"error: no type named 'type' in 'struct std::enable_if<false, float>'",例如:

int main()
{
  uint8_t rawBytes[]{ 1u, 2u, 3u, 4u };
  auto x{ parse<data_types::single_bytes>(rawBytes) };
}

如何定义一个依赖类型,只有在没有有效选项的情况下才会失败?

英文:

std::conditional_t can be nested perpetually:

#include &lt;cstdint&gt;
#include &lt;cstring&gt;
#include &lt;type_traits&gt;

enum class data_types { single_bytes, four_byte_integrals, four_byte_floats };
    
template&lt;data_types expected_type&gt;
std::conditional_t&lt;expected_type == data_types::single_bytes,        uint8_t,
std::conditional_t&lt;expected_type == data_types::four_byte_integrals, int32_t,
                                                                     float&gt;&gt;
parse(const uint8_t * rawBytes)
{
    using return_t = std::conditional_t&lt;expected_type == data_types::single_bytes,        uint8_t,
                     std::conditional_t&lt;expected_type == data_types::four_byte_integrals, int32_t,
                                                                                          float&gt;&gt;;
    constexpr auto length = sizeof(return_t);
    return_t result;
    std::memcpy(&amp;result, rawBytes, length);
    return result;
}

However, this leaves the last option as the default case, if all conditions fail. This may be undesirable.

std::enable_if exists to prevent template instantiation if a condition is not fulfilled:

#include &lt;cstdint&gt;
#include &lt;cstring&gt;
#include &lt;type_traits&gt;

enum class data_types { single_bytes, four_byte_integrals, four_byte_floats };

template&lt;data_types expected_type&gt;
std::enable_if_t&lt;expected_type == data_types::four_byte_floats , float&gt;
parse(const uint8_t * rawBytes)
{
    float result;
    constexpr auto length = sizeof(float);
    std::memcpy(&amp;result, rawBytes, length);
    return result;
}

But nesting std::enable_if into std::conditional fails always, even if an earlier branch would suffice (std::conditional does not short-circuit):

#include &lt;cstdint&gt;
#include &lt;cstring&gt;
#include &lt;type_traits&gt;

enum class data_types { single_bytes, four_byte_integrals, four_byte_floats };
    
template&lt;data_types expected_type&gt;
std::conditional_t&lt;expected_type == data_types::single_bytes,        uint8_t,
std::conditional_t&lt;expected_type == data_types::four_byte_integrals, int32_t,
std::enable_if_t&lt;expected_type == data_types::four_byte_floats,      float&gt;&gt;&gt;
parse(const uint8_t * rawBytes)
{
    using return_t = std::conditional_t&lt;expected_type == data_types::single_bytes,        uint8_t,
                     std::conditional_t&lt;expected_type == data_types::four_byte_integrals, int32_t,
                     std::enable_if_t&lt;expected_type == data_types::four_byte_floats,      float&gt;&gt;&gt;;
    constexpr auto length = sizeof(return_t);
    return_t result;
    std::memcpy(&amp;result, rawBytes, length);
    return result;
}

emits "error: no type named 'type' in 'struct std::enable_if<false, float>'" for e.g. this use:

int main()
{
  uint8_t rawBytes[]{ 1u, 2u, 3u, 4u };
  auto x{ parse&lt;data_types::single_bytes&gt;(rawBytes) };
}

How can we define a dependent type that will fail if no option is valid, but only if no option is valid?

答案1

得分: 5

使用辅助类将枚举值映射到类型,并在枚举值无效时不提供成员类型别名

enum class data_types { single_bytes, four_byte_integrals, four_byte_floats };
template<data_types expected_type>
struct enum_to_type { };
template<>
struct enum_to_type<data_types::single_bytes>        { using type = uint8_t; };
template<>
struct enum_to_type<data_types::four_byte_integrals> { using type = int32_t; };
template<>
struct enum_to_type<data_types::four_byte_floats>    { using type = float; };

然后像这样使用它

```cpp
template<data_types expected_type>
typename enum_to_type<expected_type>::type
parse(const uint8_t * rawBytes)
{
  using return_t = typename enum_to_type<expected_type>::type;
  // ...
}

演示


<details>
<summary>英文:</summary>

Use helper classes to map enum values to types, and do not provide member type aliases when the enum value is invalid

    enum class data_types { single_bytes, four_byte_integrals, four_byte_floats };
    template&lt;data_types expected_type&gt;
    struct enum_to_type { };
    template&lt;&gt;
    struct enum_to_type&lt;data_types::single_bytes&gt;        { using type = uint8_t; };
    template&lt;&gt;
    struct enum_to_type&lt;data_types::four_byte_integrals&gt; { using type = int32_t; };
    template&lt;&gt;
    struct enum_to_type&lt;data_types::four_byte_floats&gt;    { using type = float; };

and then use it like this

    template&lt;data_types expected_type&gt;
    typename enum_to_type&lt;expected_type&gt;::type
    parse(const uint8_t * rawBytes)
    {
      using return_t = typename enum_to_type&lt;expected_type&gt;::type;
      // ...
    }

[Demo](https://godbolt.org/z/aWhxo8s9x)

</details>



# 答案2
**得分**: 2

```cpp
使用 `std::type_identity` 可能会延迟实例化,并使用额外的 `::type` std::conditional_t&lt;&gt;::type

```cpp
using type =
    typename std::conditional_t&lt;
      expected_type == data_types::single_bytes, std::type_identity&lt;uint8_t&gt;,
      std::conditional_t&lt;expected_type == data_types::four_byte_integrals, std::type_identity&lt;int32_t&gt;,
      std::enable_if&lt;expected_type == data_types::four_byte_floats, float&gt;&gt;

//               ^^^^ 这里没有 _t
      &gt;::type; // 这里有额外的 `::type`,用于 conditional_t 内部的类型

<details>
<summary>英文:</summary>

You might delay instantiation with `std::type_identity` and use extra `::type` std::conditional_t&lt;&gt;::type

using type =
typename std::conditional_t<
expected_type == data_types::single_bytes, std::type_identity<uint8_t>,
std::conditional_t<expected_type == data_types::four_byte_integrals, std::type_identity<int32_t>,
std::enable_if<expected_type == data_types::four_byte_floats, float>>

// ^^^^ no _t here
>::type; // extra ::type here, for the type inside conditional_t


</details>



# 答案3
**得分**: 1

提取返回类型到自己的别名模板中,使用 `void` 作为默认值,然后在 `std::enable_if` 中使用它:

```cpp
template<data_types expected_type>
using parse_return_t = std::conditional_t<
    expected_type == data_types::single_bytes, uint8_t,
    std::conditional_t<
        expected_type == data_types::four_byte_integrals, int32_t,
        std::conditional_t<
            expected_type == data_types::four_byte_floats, float, void
        >
    >
>;

template<data_types expected_type>
std::enable_if_t<!std::is_same_v<parse_return_t<expected_type>, void>, parse_return_t<expected_type>>
parse(const uint8_t * rawBytes)
{
    constexpr auto length = sizeof(parse_return_t<expected_type>);
    parse_return_t<expected_type> result;
    std::memcpy(&result, rawBytes, length);
    return result;
}

演示

如果不想使用 void 作为默认值,可以创建另一种类型,比如 struct InvalidType {};

英文:

Extract the return type into its own alias template, with void as default, and then use that with std::enable_if:

template&lt;data_types expected_type&gt;
using parse_return_t = std::conditional_t&lt;
    expected_type == data_types::single_bytes, uint8_t,
    std::conditional_t&lt;
        expected_type == data_types::four_byte_integrals, int32_t,
        std::conditional_t&lt;
            expected_type == data_types::four_byte_floats, float, void
        &gt;
    &gt;
&gt;;

template&lt;data_types expected_type&gt;
std::enable_if_t&lt;!std::is_same_v&lt;parse_return_t&lt;expected_type&gt;, void&gt;, parse_return_t&lt;expected_type&gt;&gt;
parse(const uint8_t * rawBytes)
{
    constexpr auto length = sizeof(parse_return_t&lt;expected_type&gt;);
    parse_return_t&lt;expected_type&gt; result;
    std::memcpy(&amp;result, rawBytes, length);
    return result;
}

Demo

If you don't want void as default, you can just create another type, like struct InvalidType {};.

答案4

得分: 0

模板工具来自于type_traits非常有用,但在某些情况下,自己动手更加方便。我认为这是你的情况:

enum class data_types { single_bytes, four_byte_integrals, four_byte_floats };

template<data_types expected_type>
struct type_for_parsing;

template<>
struct type_for_parsing<data_types::single_bytes>
{
    using type = uint8_t;
};

template<>
struct type_for_parsing<data_types::four_byte_integrals>
{
    using type = int32_t;
};

template<>
struct type_for_parsing<data_types::four_byte_floats>
{
    using type = float;
};

template<data_types expected_type>
using type_for_parsing_t = typename type_for_parsing<expected_type>::type;

template<data_types expected_type>
type_for_parsing_t<expected_type> parse(const uint8_t * rawBytes);
英文:

Template tooling from type_traits is useful, but there are some cases where it is more handy to Do It Yourself. I think this is your case:

enum class data_types { single_bytes, four_byte_integrals, four_byte_floats };

template&lt;data_types expected_type&gt;
struct type_for_parsing;

template&lt;&gt;
struct type_for_parsing&lt;data_types::single_bytes&gt;
{
    using type = uint8_t;
};

template&lt;&gt;
struct type_for_parsing&lt;data_types::four_byte_integrals&gt;
{
    using type = int32_t;
};

template&lt;&gt;
struct type_for_parsing&lt;data_types::four_byte_floats&gt;
{
    using type = float;
};

template&lt;data_types expected_type&gt;
using type_for_parsing_t = typename type_for_parsing&lt;expected_type&gt;::type;

template&lt;data_types expected_type&gt;
type_for_parsing_t&lt;expected_type&gt; parse(const uint8_t * rawBytes);

huangapple
  • 本文由 发表于 2023年7月14日 00:22:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/76681508.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定