在两个不同的命名函数之间进行选择的特征有什么替代方法?
What's an alternative to traits for selecting between two different named functions?
还有...我不想使用函数指针,我真的想直接使用函数本身(因此它可以内联或应用其他优化)。
假设:我有一个模板 function/class 将要计算一些数学内容,模板参数是整数类型,可能是 unsigned int32_t
也可能是 unsigned int64_t
.
有时我需要随机数,所以我需要一个生成器,在一种情况下我将使用 mt19937
,在另一种情况下我将使用 mt19937_64
。所以实际的类型名称是不同的,但我必须选择一个并将其实际写在源代码中。
显然,整数类型的特征 class 可以正常工作(这就是我现在正在做的)。但在我看来,这种一次性使用在语法方面相当重量级,而且有点非本地(w.r.t。如果你明白我的意思,请阅读源代码)。
另一种方法是将生成器的使用封装在一些(通用)函数中,并为我的两个整数类型提供它的完全特化。这实际上没关系。
但是:还有其他选择吗?我可以在这里使用某种编译时 "if" 或 "switch"(不完全是 enable-if
实例化的 enables/disables 模板)吗?或者其他东西(也许比我只是没有看到的模板元编程更简单)?
(P.S。请不要挂断 mt19937
和 mt19937_64
- 我知道它们都是我可以用我的整数类型实例化自己的类型的别名 -但我更愿意使用标准定义及其大量非常神奇的数字。另外,我不仅对 mt19937
/mt19937_64
感兴趣,而且对其他类似情况也感兴趣。)
这是我的特征代码 class 目前的样子:
template <class Base>
struct traits { };
template <>
struct traits<unsigned __int32>
{
using base_t = unsigned __int32;
static const int nbits = std::numeric_limits<base_t>::digits;
using random_engine_t = std::mt19937;
...
};
template <>
struct traits<unsigned __int64>
{
using base_t = unsigned __int64;
static const int nbits = std::numeric_limits<base_t>::digits;
using random_engine_t = std::mt19937_64;
...
};
方法 #1:生成干净的分布式特征类型。
一些实用程序:
template<class T>struct tag_t{using type=T;};
template<class T>constexpr tag_t<T> tag = {};
template<class Tag>using type=typename Tag::type;
现在,我们创建标记重载,而不是特征 classes:
tag<std::mt1337> which_random_engine( tag_t<int> );
tag<std::mt1337_64> which_random_engine( tag_t<std::int64_t> );
它可以让你在任何地方进行这样的重载。
我们可以用它来定义一个特征class:
template<class T>
using random_engine_for = type<decltype(which_random_engine(tag<T>))>;
使用:
random_engine_for<T> engine;
方法 #2:
template<class A, class B> struct zpair_t {};
template<class T, class...> struct lookup_t {};
template<class T, class A, class B, class...Ts>
struct lookup_t<T, zpair_t<A, B>, Ts...>:lookup_t<T, Ts...>{};
template<class T, class B, class...Ts>
struct lookup_t<T, zpair_t<T, B>, Ts...>:tag_t<B>{};
template<class T, class Default, class...Ts>
struct lookup_t<T, tag_t<Default>, Ts...>:tag_t<Default>{
static_assert(sizeof(Ts...)==0, "Default must be last");
};
template<class A, class B> using kv=zpair_t<A,B>;
template<class Default> using otherwise=tag_t<Default>;
template<class T, class...KVs>
using lookup = type<lookup_t<T, KVs...>>;
using random_engine_t =
lookup< T,
kv< int, std::mt19937 >,
kv< std::int64_t, std::mt19937_64 >,
otherwise<void> // optional
>;
它使用一些相同的实用程序,并执行编译时类型映射。
我确定 boost
在语法上有更好的变化。
您可以使用 std::conditional
:
template <class Int>
using engine_t = std::conditional_t<
std::is_same<Int, uint32_t>{},
std::mt19937,
std::mt19937_64
>;
假设Int
只能是uint32_t
或uint64_t
。您最终拥有的类型越多,这就会变得越来越复杂。它还存在安全问题——如果现在支持 uint16_t
会怎么样?您最终会默默地使用 mt19937_64
,这可能不是正确的决定。
您也可以使用 mpl::map
方法:
using engine_map = mpl::map<
mpl::pair<uint32_t, std::mt19937>,
mpl::pair<uint64_t, std::mt19937_64>
>;
template <class Int>
using engine_t = typename mpl::at<engine_map, Int>::type;
这更好地扩展到更多类型。如果您引入一种新类型,这也将是一个硬编译错误,这可能是一种更好的方法。
这是否比特征更好或更差 class 我认为这是一个偏好问题,取决于您项目的其余部分。
还有...我不想使用函数指针,我真的想直接使用函数本身(因此它可以内联或应用其他优化)。
假设:我有一个模板 function/class 将要计算一些数学内容,模板参数是整数类型,可能是 unsigned int32_t
也可能是 unsigned int64_t
.
有时我需要随机数,所以我需要一个生成器,在一种情况下我将使用 mt19937
,在另一种情况下我将使用 mt19937_64
。所以实际的类型名称是不同的,但我必须选择一个并将其实际写在源代码中。
显然,整数类型的特征 class 可以正常工作(这就是我现在正在做的)。但在我看来,这种一次性使用在语法方面相当重量级,而且有点非本地(w.r.t。如果你明白我的意思,请阅读源代码)。
另一种方法是将生成器的使用封装在一些(通用)函数中,并为我的两个整数类型提供它的完全特化。这实际上没关系。
但是:还有其他选择吗?我可以在这里使用某种编译时 "if" 或 "switch"(不完全是 enable-if
实例化的 enables/disables 模板)吗?或者其他东西(也许比我只是没有看到的模板元编程更简单)?
(P.S。请不要挂断 mt19937
和 mt19937_64
- 我知道它们都是我可以用我的整数类型实例化自己的类型的别名 -但我更愿意使用标准定义及其大量非常神奇的数字。另外,我不仅对 mt19937
/mt19937_64
感兴趣,而且对其他类似情况也感兴趣。)
这是我的特征代码 class 目前的样子:
template <class Base>
struct traits { };
template <>
struct traits<unsigned __int32>
{
using base_t = unsigned __int32;
static const int nbits = std::numeric_limits<base_t>::digits;
using random_engine_t = std::mt19937;
...
};
template <>
struct traits<unsigned __int64>
{
using base_t = unsigned __int64;
static const int nbits = std::numeric_limits<base_t>::digits;
using random_engine_t = std::mt19937_64;
...
};
方法 #1:生成干净的分布式特征类型。
一些实用程序:
template<class T>struct tag_t{using type=T;};
template<class T>constexpr tag_t<T> tag = {};
template<class Tag>using type=typename Tag::type;
现在,我们创建标记重载,而不是特征 classes:
tag<std::mt1337> which_random_engine( tag_t<int> );
tag<std::mt1337_64> which_random_engine( tag_t<std::int64_t> );
它可以让你在任何地方进行这样的重载。
我们可以用它来定义一个特征class:
template<class T>
using random_engine_for = type<decltype(which_random_engine(tag<T>))>;
使用:
random_engine_for<T> engine;
方法 #2:
template<class A, class B> struct zpair_t {};
template<class T, class...> struct lookup_t {};
template<class T, class A, class B, class...Ts>
struct lookup_t<T, zpair_t<A, B>, Ts...>:lookup_t<T, Ts...>{};
template<class T, class B, class...Ts>
struct lookup_t<T, zpair_t<T, B>, Ts...>:tag_t<B>{};
template<class T, class Default, class...Ts>
struct lookup_t<T, tag_t<Default>, Ts...>:tag_t<Default>{
static_assert(sizeof(Ts...)==0, "Default must be last");
};
template<class A, class B> using kv=zpair_t<A,B>;
template<class Default> using otherwise=tag_t<Default>;
template<class T, class...KVs>
using lookup = type<lookup_t<T, KVs...>>;
using random_engine_t =
lookup< T,
kv< int, std::mt19937 >,
kv< std::int64_t, std::mt19937_64 >,
otherwise<void> // optional
>;
它使用一些相同的实用程序,并执行编译时类型映射。
我确定 boost
在语法上有更好的变化。
您可以使用 std::conditional
:
template <class Int>
using engine_t = std::conditional_t<
std::is_same<Int, uint32_t>{},
std::mt19937,
std::mt19937_64
>;
假设Int
只能是uint32_t
或uint64_t
。您最终拥有的类型越多,这就会变得越来越复杂。它还存在安全问题——如果现在支持 uint16_t
会怎么样?您最终会默默地使用 mt19937_64
,这可能不是正确的决定。
您也可以使用 mpl::map
方法:
using engine_map = mpl::map<
mpl::pair<uint32_t, std::mt19937>,
mpl::pair<uint64_t, std::mt19937_64>
>;
template <class Int>
using engine_t = typename mpl::at<engine_map, Int>::type;
这更好地扩展到更多类型。如果您引入一种新类型,这也将是一个硬编译错误,这可能是一种更好的方法。
这是否比特征更好或更差 class 我认为这是一个偏好问题,取决于您项目的其余部分。