在两个不同的命名函数之间进行选择的特征有什么替代方法?

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。请不要挂断 mt19937mt19937_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_tuint64_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 我认为这是一个偏好问题,取决于您项目的其余部分。