【发布时间】:2012-09-20 06:22:45
【问题描述】:
简介
我刚刚开始阅读和研究 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 必须是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;
};
在上面的代码中,检测 tye T 类型是否需要 const_iterator,并且容器是一个映射,也需要对 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。谈论 检测类(例如检测可迭代和配对的类),在一些 blogs articles 和有关 SFINAE 的网络示例中,我看到这是创建的常见做法一个
true_type和false_typetypedefs,通常定义为char和char[2];但我发现有些作者使用char和long作为true_type和false_type。 谁知道哪一个是最佳实践或最标准一个?
请注意,我不是在寻找为什么你不尝试“这个库”或“这个工具”之类的答案,我的目标是练习和理解 SFINAE,任何线索和建议都是欢迎光临。
【问题讨论】:
-
1)。因为数组名——它是指向它的第一个单元格的指针。它们被识别为指针。因此,您有模棱两可的电话。我建议您使用另一个版本的重载
SizeOf来作为指针。使用sizeof( _array ) / sizeof( _array[0] )计算数组大小更好。 2)。在这种情况下,应该使用模板参数作为模板。像:template <typename T, template <typename> class Container> std::enable_if<...>::type SizeOf( const T& _value )在里面你可以声明一个变量Container<T>。它将专门用于 std::basic_string。 -
3)。没有区别,因为 long 和 char 的大小不同(我希望在所有编译器中)。
-
@Pie_Jesu:不,数组名不是指向其第一个元素的指针。它可以转换为一。这在重载解决方案中至关重要,重载解决方案是根据最佳 conversion 序列决定的。
-
@MSalters 是对的,最近R. Martinho Fernandes 建议我阅读this answer 以了解
array[]与pointer的关系..
标签: c++ templates metaprogramming sfinae