英文:
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 <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;
}
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 <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;
}
But nesting std::enable_if
into std::conditional
fails always, even if an earlier branch would suffice (std::conditional
does not short-circuit):
#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;
}
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<data_types::single_bytes>(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<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; };
and then use it like this
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;
// ...
}
[Demo](https://godbolt.org/z/aWhxo8s9x)
</details>
# 答案2
**得分**: 2
```cpp
使用 `std::type_identity` 可能会延迟实例化,并使用额外的 `::type` std::conditional_t<>::type
```cpp
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>>
// ^^^^ 这里没有 _t
>::type; // 这里有额外的 `::type`,用于 conditional_t 内部的类型
<details>
<summary>英文:</summary>
You might delay instantiation with `std::type_identity` and use extra `::type` std::conditional_t<>::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<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;
}
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<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);
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论