模板专用化与标准::enable_if<>

Template specialization with std::enable_if<>

本文关键字:专用 lt gt if 标准 enable      更新时间:2023-10-16
编译

并运行以下代码:

#include <cinttypes>
#include <cstdlib>
#include <iostream>
#include <limits>
#include <sstream>
#include <stdexcept>
class UnsignedBox {
public:
    typedef std::uint64_t box_type;
    template<typename UNSIGNED_TYPE, 
        typename std::enable_if<
        std::numeric_limits<UNSIGNED_TYPE>::is_signed==false &&
        (sizeof(UNSIGNED_TYPE) >= sizeof(UnsignedBox::box_type)), int>::type = 0 
    >
    UNSIGNED_TYPE toUnsigned()const {
        //We've established we're not returning a smaller type so we can just 
       //return our value.
        return value;
    }
    template<typename UNSIGNED_TYPE, 
       typename std::enable_if<std::numeric_limits<UNSIGNED_TYPE>::is_signed==false &&
       (sizeof(UNSIGNED_TYPE) < sizeof(UnsignedBox::box_type)), int>::type = 0
    >
    UNSIGNED_TYPE toUnsigned()const {
        //We are returning  a smaller type so we need a range check.
        if(value>static_cast<box_type>(std::numeric_limits<UNSIGNED_TYPE>::max())){
            std::ostringstream msg;
            msg<<value<<'>'<<
               static_cast<box_type>(std::numeric_limits<UNSIGNED_TYPE>::max());
            throw std::logic_error(msg.str());
        }
        return value;
    }
    UnsignedBox(const box_type ivalue): value(ivalue){}
private:
    box_type value;
};
int main(int argc, char*argv[]) {
    UnsignedBox box(
        static_cast<UnsignedBox::box_type>(    
           std::numeric_limits<std::uint32_t>::max())+10
        );
    std::uint64_t v(box.toUnsigned<std::uint64_t>());
    std::cout<<v<<std::endl;
    try {
        std::uint32_t v(box.toUnsigned<std::uint32_t>());
    }catch(const std::logic_error err){
        std::cout<<err.what()<<std::endl;
    }
    return EXIT_SUCCESS;
}

预期输出(所有支持平台):

4294967305
4294967305>4294967295

目前为止,一切都好。

但我真正想做的(为了代码清晰):

声明类似以下内容:

template<typename UNSIGNED_TYPE>
UNSIGNED_TYPE toUnsigned()const;

然后提供专门的实现,例如:

template<typename UNSIGNED_TYPE, 
        typename std::enable_if<
        std::numeric_limits<UNSIGNED_TYPE>::is_signed==false &&
        (sizeof(UNSIGNED_TYPE) >= sizeof(UnsignedBox::box_type)), int>::type = 0 
    >
    UNSIGNED_TYPE UnsignedBox::toUnsigned()const {
        //We've established we're not returning a smaller type so we can just 
       //return our value.
        return value;
    }

    template<typename UNSIGNED_TYPE, 
       typename std::enable_if<std::numeric_limits<UNSIGNED_TYPE>::is_signed==false &&
       (sizeof(UNSIGNED_TYPE) < sizeof(UnsignedBox::box_type)), int>::type = 0
    >
    UNSIGNED_TYPE UnsignedBox::toUnsigned()const {
        //We are returning  a smaller type so we need a range check.
        if(value>static_cast<box_type>(std::numeric_limits<UNSIGNED_TYPE>::max())){
            std::ostringstream msg;
            msg<<value<<'>'<<
               static_cast<box_type>(std::numeric_limits<UNSIGNED_TYPE>::max());
            throw std::logic_error(msg.str());
        }
        return value;
    }

但是我收到此错误:

xxx.cpp:nn:20: error: prototype for 'UNSIGNED_TYPE UnsignedBox::toUnsigned() const' does not match any in class 'UnsignedBox'
      UNSIGNED_TYPE UnsignedBox::toUnsigned()const {
                    ^ xxx.cpp:nn:23: error: candidate is: template<class UNSIGNED_TYPE> UNSIGNED_TYPE UnsignedBox::toUnsigned() const
         UNSIGNED_TYPE toUnsigned()const;
                       ^

这很奇怪,因为如果你问我原型

UNSIGNED_TYPE UnsignedBox::toUnsigned() const

非常适合

UNSIGNED_TYPE toUnsigned()const;

我做错了什么?

PS:这不是实际问题,但我的问题类似于我想根据编译时检查的基元类型的属性来特殊一些模板。

不能用一个签名声明函数:

template<typename UNSIGNED_TYPE>
UNSIGNED_TYPE toUnsigned() const;

然后使用不同的签名定义它:

template<typename UNSIGNED_TYPE, 
    typename std::enable_if<
    std::numeric_limits<UNSIGNED_TYPE>::is_signed==false &&
    (sizeof(UNSIGNED_TYPE) >= sizeof(UnsignedBox::box_type)), int>::type = 0 
>
UNSIGNED_TYPE UnsignedBox::toUnsigned() const;

第一个需要一个模板参数,第二个需要两个 - 即使一个是默认的。两者必须完全匹配。因此,您将需要两个声明:

template <typename UNSIGNED_TYPE,
          typename = typename std::enable_if<
                        std::is_unsigned<UNSIGNED_TYPE>::value &&
                        sizeof(UNSIGNED_TYPE) >= sizeof(UnsignedBox::box_type)
                        >::type>
UNSIGNED_TYPE toUnsigned() const;
template <typename UNSIGNED_TYPE,
          typename = typename std::enable_if<
                        std::is_unsigned<UNSIGNED_TYPE>::value &&
                        sizeof(UNSIGNED_TYPE) < sizeof(UnsignedBox::box_type)
                        >::type>
UNSIGNED_TYPE toUnsigned() const;

然后是两个定义。这也本身不起作用,因为我们有效地重新定义了默认模板参数,因此您需要在返回类型上 SFINAE,例如:

template <typename UNSIGNED_TYPE>
typename std::enable_if<
    std::is_unsigned<UNSIGNED_TYPE>::value &&
    sizeof(UNSIGNED_TYPE) >= sizeof(UnsignedBox::box_type),
    UNSIGNED_TYPE>::type
toUnsigned() const;

template <typename UNSIGNED_TYPE>
typename std::enable_if<
    std::is_unsigned<UNSIGNED_TYPE>::value &&
    sizeof(UNSIGNED_TYPE) < sizeof(UnsignedBox::box_type),
    UNSIGNED_TYPE>::type
toUnsigned() const;

尽管让一个toUnsigned()基于sizeof转发到另外两个成员函数可能更简单:

template <typename UNSIGNED_TYPE,
          typename = typename std::enable_if<std::is_unsigned<UNSIGNED_TYPE>::value>::type>
UNSIGNED_TYPE toUnsigned() const {
    return toUnsigned<UNSIGNED_TYPE>(
        std::integral_constant<bool, 
            (sizeof(UNSIGNED_TYPE) >= sizeof(UnsignedBox::box_type))>{});
}
template <typename UNSIGNED_TYPE>
UNSIGNED_TYPE toUnsigned(std::true_type /* bigger */);
template <typename UNSIGNED_TYPE>
UNSIGNED_TYPE toUnsigned(std::false_type /* smaller */);

根据Barry(上图)的一些见解,我已经确定了我的答案应该是什么:

#include <cinttypes>
#include <cstdlib>
#include <iostream>
#include <limits>
#include <sstream>
#include <stdexcept>

//Here is the real aim - a short and sweet class declaration pretty much free
//of implementation junk and jiggery-pokery.
class UnsignedBox {
public:
    typedef std::uint64_t box_type;
    template<typename UNSIGNED_TYPE> UNSIGNED_TYPE toUnsigned()const;
    UnsignedBox(const box_type ivalue): value(ivalue){}
private:
    box_type value;
};
//Now things get a bit more verbose...
namespace UnsignedBox_support {
    template<
        typename FROM_TYPE,
        typename TO_TYPE,
        bool IS_UNSIGNED=(std::numeric_limits<TO_TYPE>::is_signed==false),
        bool FROM_IS_LARGER=(sizeof(FROM_TYPE)>sizeof(TO_TYPE))
    >
    class ToUnsigned{ };
    template<typename FROM_TYPE,typename TO_TYPE>
    class ToUnsigned<FROM_TYPE,TO_TYPE,true,false>{
        template<typename UNSIGNED_TYPE> 
            friend UNSIGNED_TYPE UnsignedBox::toUnsigned()const;
        static TO_TYPE convert(const FROM_TYPE v){
            //No checking...
            return static_cast<TO_TYPE>(v);
        }
    };
    template<typename FROM_TYPE,typename TO_TYPE>
    class ToUnsigned<FROM_TYPE,TO_TYPE,true,true>{
        template<typename UNSIGNED_TYPE> 
            friend UNSIGNED_TYPE UnsignedBox::toUnsigned()const;
        static TO_TYPE convert(const FROM_TYPE v){
            if(v>static_cast<FROM_TYPE>(std::numeric_limits<TO_TYPE>::max())){
                std::ostringstream msg;
                msg<<v<<'>'<<
                    static_cast<FROM_TYPE>(std::numeric_limits<TO_TYPE>::max());
                throw std::logic_error(msg.str());
            }             
            return static_cast<TO_TYPE>(v);
        }
    };
}
template<typename UNSIGNED_TYPE> UNSIGNED_TYPE UnsignedBox::toUnsigned()const{
    return UnsignedBox_support::ToUnsigned<
        UnsignedBox::box_type,UNSIGNED_TYPE
    >::convert(this->value);    
    //TEMPLATE USE DEBUGGING:
    //If you find yourself here being told ToUnsigned has no member
    //convert() then it's possible you're trying to implement 
    //this member for a signed data-type.
}
int main(int argc, char*argv[]) {
    UnsignedBox box(
        static_cast<UnsignedBox::box_type>(std::numeric_limits<std::uint32_t>::max())+10
    );
    std::uint64_t v(box.toUnsigned<std::uint64_t>());
    std::cout<<v<<std::endl;
    try {
        std::uint32_t v(box.toUnsigned<std::uint32_t>());
    }catch(const std::logic_error err){
        std::cout<<err.what()<<std::endl;
    }
    return EXIT_SUCCESS;
}

诀窍是实现一个成员函数(不能部分专用)并让它调用一个类(可以部分专用)。

请注意,如果不在支持类中声明成员convert() ToUnsigned滥用模板并尝试为签名类型调用模板,则会引发编译错误。否则,您将面临更难跟踪的链接错误的风险。如果您确实在main()中添加了这样的行,我已经在您可能会被带到的点上添加了一条评论:

int xxx(box.toUnsigned<int>());

我不得不说,我认为这与其说是一个丑陋的黑客,不如说是任何std::enable_if<>解决方案,并且通过将支持成员设为私有并friend他们在那里帮助实施它的成员至少可以做一些工作来"封装"。

它还为进一步的专业打开了大门,以补充或覆盖已经给出的专业化。我并不是说所有模板都应该编写以允许进一步专业化,但我确实认为敞开大门是有用的。