介绍
我刚刚开始阅读和研究 SFINAE。为了提高我的理解力,我开始自己尝试一些事情。
所以我一直想知道一个有用但简单的方法来使用 SFINAE 强大的技巧,我最终想到了一组函数来计算给定类型占用多少字节;只要我们处理简单的类型,解决方案就很简单:
template <typename T> size_t SizeOf(const T &t)
{
return sizeof(T);
};
这种幼稚的近似值将得到任何东西的大小:1 代表char
,可能 4 代表int
,希望 4 代表char[4]
以及任何用于class PrettyAwesome
或struct AmazingStuff
包括填充字节的东西。但是,这种类型管理的动态内存呢?
所以我会检查给定的类型是否是指针类型,那么总大小将是指针的大小加上指向内存的大小(如果有的话)。
template <typename T> size_t SizeOf(const T &*t)
{
size_t Result = sizeof(t);
if (t)
{
Result += sizeof(T);
}
return Result;
};
是的,在这一点上似乎根本不需要 SFINAE,但是,让我们考虑一下容器。SizeOf
a 容器必须是其每个sizeof(container_type)
元素的大小之和的总和,这就是 SFINAE 进入的地方:
template <typename T> size_t SizeOf(const T &t)
{
size_t Result = sizeof(t);
for (T::const_iterator i = t.begin(); i != t.end(); ++i)
{
Result += SizeOf(*i);
}
return Result;
};
在上面的代码中,检测 tyeT
类型是否const_iterator
需要 a ,并且容器是一个映射,也需要对 pair 的特化。
问题
最后,问题从这里开始:我尝试了什么,遇到了什么问题?
#include <type_traits>
#include <string>
#include <map>
#include <iostream>
#include <vector>
// Iterable class detector
template <typename T> class is_iterable
{
template <typename U> static char has_iterator(typename U::const_iterator *);
template <typename U> static long has_iterator(...);
public:
enum
{
value = (sizeof(has_iterator<T>(0)) == sizeof(char))
};
};
// Pair class detector
template <typename T> class is_pair
{
template <typename U> static char has_first(typename U::first_type *);
template <typename U> static long has_first(...);
template <typename U> static char has_second(typename U::second_type *);
template <typename U> static long has_second(...);
public:
enum
{
value = (sizeof(has_first<T>(0)) == sizeof(char)) && (sizeof(has_second<T>(0)) == sizeof(char))
};
};
// Pointer specialization.
template <typename T> typename std::enable_if<std::is_pointer<T>::value, size_t>::type SizeOf(const T &aValue)
{
size_t Result = sizeof(aValue);
if (aValue)
{
Result += sizeof(T);
}
return Result;
}
// Iterable class specialization.
template <typename T> typename std::enable_if<is_iterable<T>::value, size_t>::type SizeOf(const T &aValue)
{
size_t Result = sizeof(aValue);
for (T::const_iterator I = aValue.begin(); I != aValue.end(); ++I)
{
Result += SizeOf(*I);
}
return Result;
}
// Pair specialization.
template <typename T> typename std::enable_if<is_pair<T>::value, size_t>::type SizeOf(const T &aValue)
{
return SizeOf(aValue.first) + SizeOf(aValue.second);
}
// Array specialization.
template <typename T> typename std::enable_if<std::is_array<T>::value, size_t>::type SizeOf(const T &aValue)
{
size_t Result = sizeof(aValue);
for (T *I = std::begin(aValue); I != std::end(aValue); ++I)
{
SizeOf(*I);
}
return Result;
}
// Other types.
template <typename T> typename std::enable_if<std::is_pod<T>::value, size_t>::type SizeOf(const T &aValue)
{
return sizeof(aValue);
}
int main(int argc, char **argv)
{
int Int;
int *IntPtr = ∬
int twoints[2] = {0, 0};
int *twointpointers[2] = {IntPtr};
std::string SO("StackOverflow");
std::wstring WSO(L"StackOverflow");
std::map<std::string, char> m;
std::vector<float> vf;
m[SO] = 'a';
std::cout << "1: " << SizeOf(Int) << '\n';
// std::cout << "2: " << SizeOf(IntPtr) << '\n';
// std::cout << "3: " << SizeOf(twoints) << '\n';
// std::cout << "4: " << SizeOf(twointpointers) << '\n';
std::cout << "5: " << SizeOf(SO) << '\n';
std::cout << "6: " << SizeOf(WSO) << '\n';
std::cout << "7: " << SizeOf(m) << '\n';
std::cout << "8: " << SizeOf(vf) << '\n';
return 0;
}
上面的代码产生这个输出:
1: 4
5: 45
6: 58
7: 66
8: 20
如果我取消注释带有 2、3 和 4 输出的行,编译器会显示“模糊调用”错误。我真的以为输出 2 将使用
is_pointer
专业化,而输出 3 和 4 将使用is_array
专业化。好吧,我错了,但我不知道为什么。我对获取容器总大小的方式不满意,我认为迭代所有项目并
SizeOf
为每个项目调用是一个不错的选择,但不是所有容器,std::basic_string
这样做sizeof(container) + sizeof(container::value_type) * container.size()
会更快,但我无法意识到如何专攻basic_string
。谈到检测类(比如检测可迭代和配对的类),在一些关于 SFINAE 的博客文章和网络示例中,我看到这是创建 a
true_type
和false_type
typedef
s 的常见做法,通常定义为char
andchar[2]
; 但我发现有些作者使用char
andlong
astrue_type
和false_type
. 任何人都知道哪一个是最佳实践或最标准的一个?
请注意,我不是在寻找诸如为什么不尝试“此库”或“此工具”之类的答案,我的目标是练习和理解 SFINAE,欢迎提供任何线索和建议。