对于出现在可变参数模板参数包的任何位置的类型的类模板的部分特化

时间:2013-09-08 00:02:36

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

我已经定义了一个充当整数的类型。我想为我的类型定义std :: common_type的特化。但是,这种专门化应该能够将bounded_integer(我的类)的common_type与任何其他bounded_integer或内置整数类型的其他参数组合在一起。我希望以下代码都有效:

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. 的第一个参数
  3. 总是使用我的make_bounded(内置整数值)函数将它们的整数转换为bounded_integer(因此没有内置类型的common_type专门化与bounded_integer一起使用)
  4. 从不将bounded_integer放在大于N的位置,其中N是我确定的某个数字,类似于Visual Studio的旧的可变参数模板解决方法
  5. 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<int, unsigned, bounded_integer<0, std::numeric_limits<unsigned>::max() + 1>

    应该给我

    bounded_integer<std::numeric_limits<int>::min(), std::numeric_limits<unsigned>::max() + 1>

    然而,它没有。它首先将common_type应用于intunsigned,它遵循标准的整数提升规则,给出unsigned。然后它返回common_type unsignedbounded_integer的结果,给出

    bounded_integer<0, std::numeric_limits<unsigned>::max() + 1>

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

    我的问题是:如何在不必通过手动写出任意数量的参数后跟bounded_integer后跟参数包来近似它,或者这是不可能的?

    编辑2:

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

    common_typeint的{​​{1}}为unsigned。例如:http://ideone.com/9IxKIW。 Standardese可以在§20.9.7.6/3中找到,其中两个值的unsigned

    common_type

    在§5.16/ 6中,它说

      

    第二个和第三个操作数具有算术或枚举类型;该   通常的算术转换被执行以使它们成为共同的   类型,结果是那种类型。

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

      

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

4 个答案:

答案 0 :(得分:3)

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特征。

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

答案 1 :(得分:2)

简短回答

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


长答案

当你说std::common_type<unsigned [long [long]] int, [long [long]] int>::type会产生unsigned [long [long]]时,你是对的。但是,由于涉及common_ type的任何表达式的ranged_integer本身都是ranged_integer,并且假设您的专业化涉及ranged_integer正确地推断出范围,因此只有成对{{} { {1}}的种类前述common_type生成[long [long]] unsigned。这使得我们只有六个情况下,我们要解决办法,即̶̶[long [long]] int̶及其排序̶p̶e̶r̶m̶u̶t̶a̶t̶i̶o̶n̶s̶.̶̶(I”中号忽略固定宽度的类型以下,̶但延伸的想法,认为它们应该是直接的)̶

W̶e̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶̶:̶

实际上我们无法根据 n3485

[meta.type.synop]第1段

“除非另有说明,否则为本子条款[̶s̶t̶d̶:̶:̶c̶o̶m̶m̶o̶n̶_̶t̶y̶p̶e̶<̶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̶>̶:̶:̶t̶y̶p̶e̶中定义的]任何类模板添加特殊化的程序的行为是未定义的。”

[meta.trans.other]表57

[...] A 如果特化中的至少一个模板参数是用户定义的类型,则程序可以专门化该特征[template <class... T> common_type]。 [...]

这意味着没有有效的方法来覆盖template <class... T> common_type 的行为,标准要求它始终按照之前的指示生成std::common_type<unsigned [long [long]] int, [long [long]] int>::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

答案 2 :(得分:1)

这是一个可能的实现:

#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;
}

答案 3 :(得分:0)

也许我错过了什么,但是你不想在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类型?