【问题标题】:Partial specialization of class template for a type that appears in any position of a variadic template parameter pack出现在可变参数模板参数包的任何位置的类型的类模板的部分特化
【发布时间】:2013-09-11 19:29:42
【问题描述】:

我已经定义了一个作为整数的类型。我想为我的类型定义 std::common_type 的专业化。但是,这种特化应该能够结合任意数量的其他参数(其他有界整数或内置整数类型)给出有界整数类型的 common_type。我希望以下代码都有效:

std::common_type<bounded_integer<1, 10>>::type
std::common_type<bounded_integer<1, 10>, int>::type
std::common_type<int, long, bounded_integer<1, 10>>::type
std::common_type<int, int, long, short, long long, short, bounded_integer<1, 10>, int, short, short, short, ..., short, bounded_integer<1, 10>>::type

我第一次尝试解决这个问题是使用 enable_if。但是,我意识到这不允许我与 common_type 的库定义区分开来,因为我所拥有的本质上是

#include <type_traits>

class C {};

template<typename T, typename... Ts>
class contains_c {
public:
        static constexpr bool value = contains_c<T>::value or contains_c<Ts...>::value;
};
template<typename T>
class contains_c<T> {
public:
        static constexpr bool value = std::is_same<T, C>::value;
};

namespace std {

template<typename... Args, typename std::enable_if<contains_c<Args...>::value>::type>
class common_type<Args...> {
public:
        using type = C;
};

}       // namespace std

int main() {
}

“部分专业化”实际上只是“任何参数”,它并不比我们所拥有的更专业化。

因此,似乎唯一的解决方案是要求我的用户执行以下操作之一:

  1. 始终将 bounded_integer 作为 common_type 的第一个参数
  2. 始终使用我的 make_bounded(built-in integer value) 函数将它们的整数转换为 bounded_integer(因此对于内置类型与 bounded_integer 的组合,没有专门的 common_type)
  3. 永远不要将 bounded_integer 放在大于 N 的位置,其中 N 是我确定的某个数字,类似于 Visual Studio 的旧可变参数模板解决方法

3 看起来像这样:

// all_bounded_integer_or_integral and all_are_integral defined elsewhere with obvious definitions
template<intmax_t minimum, intmax_t maximum, typename... Ts, typename = type std::enable_if<all_bounded_integer_or_integral<Ts...>::value>::type>
class common_type<bounded_integer<minimum, maximum>, Ts...> {
};
template<typename T1, intmax_t minimum, intmax_t maximum, typename... Ts, typename = typename std::enable_if<all_are_integral<T1>::value>::type, typename = typename std::enable_if<all_bounded_integer_or_builtin<Ts...>::value>::type>
class common_type<T1, bounded_integer<minimum, maximum>, Ts...> {
};
template<typename T1, typename T2, intmax_t minimum, intmax_t maximum, typename... Ts, typename = typename std::enable_if<all_are_integral<T1, T2>::value>::type, typename = typename std::enable_if<all_bounded_integer_or_builtin<Ts...>::value>::type>
class common_type<T1, T2, bounded_integer<minimum, maximum>, Ts...> {
};
// etc.

对于我无法更改其原始定义的类,是否有更好的方法来完成此任务(当所有类型满足一个条件并且任何类型满足另一个条件时的模板特化)?

编辑:

根据答案,我的问题不够清楚。

首先,预期的行为:

如果有人调用 std::common_type 并且所有类型都是 bounded_integer 的实例或内置数字类型,我希望结果是一个 bounded_integer,它具有所有可能的最小值和最大值所有可能的最大值。

问题:

当有人在任意数量的 bounded_integer 上调用 std::common_type 时,我有一个可行的解决方案。但是,如果我只专门化两个参数的版本,那么我会遇到以下问题:

std::common_type&lt;int, unsigned, bounded_integer&lt;0, std::numeric_limits&lt;unsigned&gt;::max() + 1&gt;

应该给我

bounded_integer&lt;std::numeric_limits&lt;int&gt;::min(), std::numeric_limits&lt;unsigned&gt;::max() + 1&gt;

但是,事实并非如此。它首先将 common_type 应用于 intunsigned,这遵循标准的整数提升规则,给出 unsigned。然后它返回common_typeunsigned 和我的bounded_integer 的结果,给出

bounded_integer&lt;0, std::numeric_limits&lt;unsigned&gt;::max() + 1&gt;

因此,通过在参数包的中间添加unsigned,即使它对结果类型绝对没有影响(其范围完全包含在所有其他类型的范围内),它仍然会影响结果。我能想到的唯一防止这种情况发生的方法是将std::common_type 专门用于任意数量的内置整数,后跟bounded_integer,后跟任意数量的内置整数或bounded_integer

我的问题是:我怎样才能做到这一点,而不必手动写出任意数量的参数,然后是bounded_integer,然后是参数包,或者这是不可能的?

编辑 2:

common_type 会给出错误值的原因可以通过这个遵循标准的推理来解释(引自 N3337)

intunsignedcommon_typeunsigned。例如:http://ideone.com/9IxKIW。标准语可以在 § 20.9.7.6/3 中找到,其中两个值的 common_type

typedef decltype(true ? declval&lt;T&gt;() : declval&lt;U&gt;()) type;

在第 5.16/6 节中,它说

第二个和第三个操作数有算术或枚举类型;这 执行通常的算术转换以将它们带到一个共同的 类型,结果就是那个类型。

通常的算术转换在第 5/9 节中定义为

否则,如果无符号整数类型的操作数有秩 大于或等于另一个操作数的类型的等级, 带符号整数类型的操作数应转换为 无符号整数类型的操作数。

【问题讨论】:

  • std::common_type&lt;ranged_integer&lt;1, 10&gt;, short&gt;::type 应该输出什么? shortranged_integer&lt;std::numeric_limits&lt;short&gt;::min(), std::numeric_limits&lt;short&gt;::max()&gt;.
  • @brunocodutra:我已经更新了我的帖子来解释。我希望包含所有ranged_integer 和整数类型的任何表达式的common_type 的结果是ranged_integer,其范围包含来自任何类型的所有可能值。

标签: c++ c++11 variadic-templates template-specialization template-meta-programming


【解决方案1】:

std::common_type 将其自己的双参数特化外推到 n 参数情况。您只需要专门化两个参数的情况。

template< typename other, int low, int high >
struct common_type< other, ::my::ranged_integer< low, high > > {
    using type = other;
};

template< typename other, int low, int high >
struct common_type< ::my::ranged_integer< low, high >, other > {
    using type = other;
};

template< int low, int high >
struct common_type< ::my::ranged_integer< low, high >,
                    ::my::ranged_integer< low, high > > {
    using type = ::my::ranged_integer< low, high >;
};

这使得不同范围整数之间的common_type 未定义。我想你可以用minmax 来做到这一点。

如果你的类支持继承,你也可以创建一个is_ranged_integer trait。

不要忘记将您的库放在命名空间中。

【讨论】:

  • @Potatoswatter 我相信 DyP 指的是额外的尾随 ::type&gt;
【解决方案2】:

简答

如果绝对需要使用标准库提供的std::common_type,那么除了您自己观察到的 3 个替代方案之外,没有更好的方法。如果用户定义的common_type 被认为是可以接受的,那么您可以实现您想要的,如下所示。


长答案

当您说std::common_type&lt;unsigned [long [long]] int, [long [long]] int&gt;::type 将产生unsigned [long [long]] 时,您是对的。但是,由于涉及ranged_integer 的任何表达式的common_ type 本身就是ranged_integer,并且考虑到涉及ranged_integer 的专业化正确推断范围,因此只有在@987654333 之前的类型的成对common_type 时才会出现问题@产量[long [long]] int。那留给我们只有六个情况下,我们要解决办法,即̶̶̶s̶t̶d̶:̶:̶c̶o̶m̶m̶o̶n̶_̶t̶y̶p̶e̶&lt;̶u̶n̶s̶i̶g̶n̶e̶d̶ ̶[̶l̶o̶n̶g̶ ̶[̶l̶o̶n̶g̶]̶]̶ ̶i̶n̶t̶,̶ ̶[̶l̶o̶n̶g̶ ̶[̶l̶o̶n̶g̶]̶]̶ ̶i̶n̶t̶&gt;̶:̶:̶t̶y̶p̶e̶̶及其排序̶p̶e̶r̶m̶u̶t̶a̶t̶i̶o̶n̶s̶.̶̶(̶I̶'̶m̶忽略固定宽度的类型以下,̶但延伸的想法来考虑̶t̶h̶e̶m̶s̶h̶o̶u̶l̶d̶̶b̶e̶̶s̶t̶r̶a̶i̶g̶h̶t̶f̶o̶r̶w̶a̶r̶d̶)̶

W̶e̶̶c̶a̶n̶̶a̶c̶h̶i̶e̶v̶e̶̶t̶h̶a̶t̶̶b̶y̶̶a̶g̶a̶i̶n̶̶p̶r̶o̶v̶i̶d̶i̶n̶g̶̶e̶x̶p̶l̶i̶c̶i̶t̶ ̶s̶p̶e̶c̶i̶a̶l̶i̶z̶a̶t̶o̶p̶n̶

其实我们不能按照n3485

[meta.type.synop] 第 1 段

“除非另有说明,否则为本子条款 [template &lt;class... T&gt; common_type 包括] 中定义的任何类模板添加特化的程序的行为是未定义的。”

[meta.trans.other] 表 57

[...] 如果专业化中的至少一个模板参数是用户定义的类型,则程序可以专门化此特征 [template &lt;class... T&gt; common_type]。 [...]"

这意味着没有有效的方法来覆盖std::common_type&lt;unsigned [long [long]] int, [long [long]] int&gt;::type 的行为,标准要求始终产生unsigned [long [long]] int,如前所述。


替代std::common_type

在应用于原始整数类型时,克服std::common_type 限制的另一种方法是定义自定义common_type

假设ranged_integer定义如下。

template<typename T, T min, T max>
struct basic_ranged_integer;

template<std::intmax_t min, std::intmax_t max>
using ranged_integer = basic_ranged_integer<std::intmax_t, min, max>;

自定义common_type 可以定义如下。

先左递归:

template<typename... T>
struct common_type;

template<typename T, typename U, typename... V>
struct common_type<T, U, V...> :
        common_type<typename common_type<T, U>::type, V...> //left recursion
{};

现在涉及basic_ranged_integer的专业化。

//two basic_ranged_integer
template<typename T, T minT, T maxT, typename U, U minU, U maxU>
struct common_type<basic_ranged_integer<T, minT, maxT>, basic_ranged_integer<U, minU, maxU>>
{
    //gory details go here
};

//basic_ranged_integer mixed with primitive integer types
//forwards to the case involving two basic_ranged_integer
template<typename T, T minT, T maxT, typename U>
struct common_type<basic_ranged_integer<T, minT, maxT>, U> :
        common_type
        <
            basic_ranged_integer<T, minT, maxT>,
            typename make_ranged_integer<U>::type
        >
{};

template<typename T, typename U, U minU, U maxU>
struct common_type<T, basic_ranged_integer<U, minU, maxU>> :
        common_type
        <
            typename make_ranged_integer<T>::type,
            basic_ranged_integer<U, minU, maxU>
        >
{};

最后是涉及有符号和无符号原始整数组合的特化。

//base case: forwards to the satandard library
template<typename T>
struct common_type<T> :
        std::common_type<T>
{};

template<typename T, typename U>
struct common_type<T, U>
{
    static constexpr bool signed_xor  = std::is_signed<T>{} xor std::is_signed<U>{};

    //base case: forwards to the satandard library
    template<bool b = signed_xor, typename = void>
    struct helper :
            std::common_type<T, U>
    {};

    //mixed signed/unsigned: forwards to the case involving two basic_ranged_integer
    template<typename _ >
    struct helper<true, _> :
            common_type<typename make_ranged_integer<T>::type, typename make_ranged_integer<U>::type>
    {};

    using type = typename helper<>::type;
};

在上面的make_ranged_integer 应该采用原始整数类型并将type 定义为所需的对应basic_ranged_integer

【讨论】:

  • 不幸的是,OVERWRITE_DEFAULT_INTEGRAL_PROMOTION_RULES(int, long long); 等仍然不够,因为可能存在无法以这种方式可移植处理的实现定义的扩展整数类型。如果您要定义自己的common_type(如建议的那样),则可以依赖?: 运算符并调整结果的签名(通过is_signedmake_signed)。
  • @DyP 你是对的,因此我在回答开始时发出警告,但由于 OP 没有提到他希望该机制对原始类型和他的 ranged_integer 有效同时,又想让std::common_type&lt;int, unsigned, ranged_integer&lt;0, std::numeric_limits&lt;unsigned&gt;::max() + 1&gt;工作,没有别的办法,必须保证std::common_type&lt;int, unsigned&gt;不会让unsigned。我应该再次指出,自定义 common-type-like 元函数是最好的。
  • 哦,我想念你已经为建议的自定义common_type 说过“其基本案例转发到标准实现”(这将解决这个问题)。好吧,那么扩展的整数类型只是在此处添加第二个警告,即专门针对 std::common_type
  • @DyP 是的,我们可以将 common_type 专门用于 ranged_integer 和这些第三方扩展整数类型的组合,但我相信这不能保证有效,因为它们与原始整数的交互可能依赖common_type 的原始行为,因此中断。
  • 我正在使用公开的草稿文件。最新的 C++14 之前的版本是 AFAIK n3485,其中包含对已发布的 C++11 标准的一些更正。另请参阅Committee's home page
【解决方案3】:

这是一个可能的实现:

#include <limits>  
#include <utility>
#include <iostream>

template<typename T, typename U>
static constexpr auto min(T x, U y) -> decltype(x < y ? x : y)
{
    return x < y ? x : y;
}

template<typename T, typename U>
static constexpr auto max(T x, U y) -> decltype(x < y ? x : y)
{
    return x > y ? x : y;
}

template<intmax_t f, intmax_t l>
struct ranged_integer
{
    static intmax_t const first = f;
    static intmax_t const last  = l;
    static_assert(l > f, "invalid range");
};

template <class ...T> struct common_type
{
};

template <class T>
struct common_type<T>
{
    typedef T type;
};

template <class T, class U>
struct common_type<T, U>
{
    typedef decltype(true ? std::declval<T>() : std::declval<U>()) type;
};

template <class T, intmax_t f, intmax_t l>
struct common_type<T, ranged_integer<f,l>>
{
    typedef ranged_integer< min(std::numeric_limits<T>::min(),f) , max(std::numeric_limits<T>::max(),l) > type;
};

template <class T, intmax_t f, intmax_t l>
struct common_type<ranged_integer<f,l>, T>
{
    typedef typename common_type<T, ranged_integer<f,l>>::type type;
};

template <intmax_t f1, intmax_t l1, intmax_t f2, intmax_t l2>
struct common_type<ranged_integer<f1,l1>, ranged_integer<f2,l2>>
{
    typedef ranged_integer< min(f1,f2) , max(l1,l2) > type;
};

template <class T, class U, class... V>
struct common_type<T, U, V...>
{
    typedef typename common_type<typename common_type<T, U>::type, V...>::type type;
};

int main(int argc, char *argv[])
{
    typedef common_type<char, ranged_integer<-99999999, 20>, short, ranged_integer<10, 999999999>, char>::type type;
    std::cout << type::first << std::endl; // -99999999
    std::cout << type::last << std::endl;  // 999999999
    return 0;
}

【讨论】:

    【解决方案4】:

    也许我遗漏了一些东西,但你不只是想为int 案例提供这样的东西吗:

    namespace std {
    
    // first give ranged_integer a ground zero
        template<intmax_t minimum, intmax_t maximum>
        class common_type<ranged_integer<minimum, maximum>> {
            typedef typename ranged_integer<minimum, maximum> type;
        };
    
    // sort out int
        template<intmax_t minimum, intmax_t maximum>
        class common_type<int, ranged_integer<minimum, maximum>> {
            typedef typename ranged_integer<minimum, maximum> type;
        };
    
        template<intmax_t minimum, intmax_t maximum>
        class common_type<ranged_integer<minimum, maximum>, int>> {
            typedef typename ranged_integer<minimum, maximum> type;
        };
    

    `longlong long...重复此操作,common_type 的模板定义将处理所有只有一个ranged_integer 类型的可变参数情况?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-08-27
      • 1970-01-01
      • 2016-10-06
      • 2017-08-13
      • 2023-04-02
      • 1970-01-01
      相关资源
      最近更新 更多