std::enable_if<> 的模板专业化
Template specialization with std::enable_if<>
编译运行以下代码:
#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;
然后是两个定义。这本身也不起作用,因为我们有效地重新定义了默认模板参数,因此您需要在 return 类型上使用 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;
}
诀窍是实现一个成员函数(不能部分特化)并调用 class(可以部分特化。
注意,不在支持 class ToUnsigned
中声明成员 convert()
滥用模板并尝试为签名类型调用它会引发编译错误。否则,您将面临更难追踪 link 错误的风险。
如果您确实向 main()
:
添加了这样一行,您可能会想到这一点,我已经添加了评论
int xxx(box.toUnsigned<int>());
我不得不说,我认为这比任何 std::enable_if<>
解决方案都不那么丑陋,并且通过将支持成员设为私有并 friend
他们在那里帮助实现它的成员至少 'encapsulate'.
的一些工作
它也为进一步的专业化补充或覆盖已经提供的专业化打开了大门。我并不是说应该编写所有模板以允许进一步专业化,但我确实认为敞开大门是有用的。
编译运行以下代码:
#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;
然后是两个定义。这本身也不起作用,因为我们有效地重新定义了默认模板参数,因此您需要在 return 类型上使用 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;
}
诀窍是实现一个成员函数(不能部分特化)并调用 class(可以部分特化。
注意,不在支持 class ToUnsigned
中声明成员 convert()
滥用模板并尝试为签名类型调用它会引发编译错误。否则,您将面临更难追踪 link 错误的风险。
如果您确实向 main()
:
int xxx(box.toUnsigned<int>());
我不得不说,我认为这比任何 std::enable_if<>
解决方案都不那么丑陋,并且通过将支持成员设为私有并 friend
他们在那里帮助实现它的成员至少 'encapsulate'.
它也为进一步的专业化补充或覆盖已经提供的专业化打开了大门。我并不是说应该编写所有模板以允许进一步专业化,但我确实认为敞开大门是有用的。