7

我已经定义了一个作为整数的类型。我想为我的类型定义 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<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 应用于intand unsigned,它遵循标准的积分提升规则,给出unsigned。然后它返回common_typewithunsigned和 my的结果bounded_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_type_ 例如:http: //ideone.com/9IxKIW。Standardese 可以在 § 20.9.7.6/3 中找到,其中两个值的intunsignedunsignedcommon_type

typedef decltype(true ? declval<T>() : declval<U>()) type;

在第 5.16/6 节中,它说

第二和第三个操作数有算术或枚举类型;执行通常的算术转换以将它们转换为通用类型,并且结果属于该类型。

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

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

4

4 回答 4

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不同范围整数之间的未定义。我想你可以用minand来做max

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

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

于 2013-09-08T00:42:52.737 回答
2

简短的回答

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


长答案

当你说std::common_type<unsigned [long [long]] int, [long [long]] int>::type会屈服时,你是对的unsigned [long [long]]。However, since the common_ typeof any expression involving ranged_integeris itself a ranged_integerand given that your specializations involving ranged_integercorrectly infer the ranges, there's only a problem if the pairwise common_typeof the types preceding [long [long]] unsignedyields [long [long]] int.T̶h̶a̶t̶ ̶l̶e̶a̶v̶e̶s̶ ̶u̶s̶ ̶o̶n̶l̶y̶ ̶s̶i̶x̶ ̶c̶a̶s̶e̶s̶ ̶w̶e̶ ̶h̶a̶v̶e̶ ̶t̶o̶ ̶w̶o̶r̶k̶a̶r̶o̶u̶n̶d̶,̶ ̶n̶a̶m̶e̶l̶y̶ ̶ ̶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̶̶ ̶a̶n̶d̶ ̶t̶h̶e̶i̶r̶ ̶o̶r̶d̶e̶r̶i̶n̶g̶ ̶p̶e̶r̶m̶u̶t̶a̶t̶i̶o̶n̶s̶.̶ ̶(̶I̶'̶m̶ ̶i̶g̶n̶o̶r̶i̶n̶g̶ ̶t̶h̶e̶ ̶f̶i̶x̶e̶d̶ ̶w̶i̶d̶t̶h̶ ̶t̶y̶p̶e̶s̶ ̶h̶e̶r̶e̶,̶ ̶b̶u̶t̶ ̶t̶h̶e̶ ̶e̶x̶t̶e̶n̶d̶i̶n̶g̶ ̶t̶h̶e̶ ̶i̶d̶e̶a̶ ̶t̶o̶ ̶c̶o̶n̶s̶i̶d̶e̶r̶ ̶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̶i̶o̶n̶s:

事实上我们不能根据 n3485

[meta.type.synop] 第 1 段

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

[meta.trans.other] 表 57

[...]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为所需的对应basic_ranged_integer.

于 2013-09-08T04:19:15.717 回答
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;
}
于 2013-09-08T01:05:04.543 回答
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;
    };

`long对,long long等重复此操作... 并且common_type's 模板定义将处理所有只有一种ranged_integer类型的可变参数情况?

于 2013-09-08T00:57:20.900 回答