我的以下代码应检测T
是否有begin
和end
方法:
template <typename T>
struct is_container
{
template <typename U, typename U::const_iterator (U::*)() const,
typename U::const_iterator (U::*)() const>
struct sfinae {};
template <typename U> static char test(sfinae<U, &U::begin, &U::end>*);
template <typename U> static long test(...);
enum { value = (1 == sizeof test<T>(0)) };
};
以下是一些测试代码:
#include <iostream>
#include <vector>
#include <list>
#include <set>
#include <map>
int main()
{
std::cout << is_container<std::vector<std::string> >::value << ' ';
std::cout << is_container<std::list<std::string> >::value << ' ';
std::cout << is_container<std::set<std::string> >::value << ' ';
std::cout << is_container<std::map<std::string, std::string> >::value << '\n';
}
在g ++ 4.5.1上,输出为1 1 1 1
。但是,在Visual Studio 2008上,输出为1 1 0 0
。我做错了什么,或者这只是一个VS 2008错误?任何人都可以测试不同的编译器吗?谢谢!
答案 0 :(得分:12)
所以,这就是我如何调试这些东西。
首先,注释掉否定的替代方案,这样你就会得到错误,而不仅仅是错配。 接下来,尝试使用其中一个不起作用的项来实例化您放入函数的类型。
在这一步,我能够实例化你的sfinae对象,但它仍然无法正常工作。 “这让我知道它是一个VS错误,所以问题就是如何修复它。” - OBS
当你按照自己的方式完成时,VS似乎对SFINAE有麻烦。 当然可以!当你包装你的sfinae对象时它会更好。我是这样做的:
template <typename U, typename it_t = typename U::const_iterator >
struct sfinae
{
// typedef typename U::const_iterator it_t; - fails to compile with non-cont types. Not sfinae
template < typename U, typename IT, IT (U::*)() const, IT (U::*)() const >
struct type_ {};
typedef type_<U,it_t,&U::begin,&U::end> type;
};
仍然无法正常工作,但至少我收到了一条有用的错误消息:
error C2440: 'specialization' : cannot convert from 'overloaded-function' to 'std::_Tree_const_iterator<_Mytree> (__thiscall std::set<_Kty>::* )(void) const'
这让我知道&U::end
不足以让VS( ANY 编译器)能够告诉我想要哪一端() static_cast修复了 :
typedef type_<U,it_t,static_cast<it_t (U::*)() const>(&U::begin),static_cast<it_t (U::*)() const>(&U::end)> type;
全部重新组合并运行测试程序......使用VS2010取得成功。你可能会发现static_cast实际上就是你所需要的,但是我把它留给你去找。
我想现在真正的问题是,哪个编译器是正确的?我的赌注是一致的:g ++。 指向聪明人:从不假设我当时做了什么。
编辑:Jeesh ...... 你错了!
更正版本:
template <typename T>
struct is_container
{
template <typename U, typename it_t = typename U::const_iterator >
struct sfinae
{
//typedef typename U::const_iterator it_t;
template < typename U, typename IT, IT (U::*)() const, IT (U::*)() const >
struct type_ {};
typedef type_<U,it_t,static_cast<it_t (U::*)() const>(&U::begin),static_cast<it_t (U::*)() const>(&U::end)> type;
};
template <typename U> static char test(typename sfinae<U>::type*);
template <typename U> static long test(...);
enum { value = (1 == sizeof test<T>(0)) };
};
#include <iostream>
#include <vector>
#include <list>
#include <set>
#include <map>
int main()
{
std::cout << is_container<std::vector<std::string> >::value << ' ';
std::cout << is_container<std::list<std::string> >::value << ' ';
std::cout << is_container<std::set<std::string> >::value << ' ';
std::cout << is_container<std::map<std::string, std::string> >::value << ' ';
std::cout << is_container<bool>::value << '\n';
}
- 上面的调试是明智的,但关于编译器的假设是错误的。由于我上面强调的原因,G ++应该失败了。
答案 1 :(得分:5)
你为什么要这么努力?如果您想检查U::begin()
是否存在,为什么不试试?
template <typename T>
struct is_container
{
template <typename U> static char test(U* u,
typename U::const_iterator b = ((U*)0)->begin(),
typename U::const_iterator e = ((U*)0)->end());
template <typename U> static long test(...);
enum { value = (1 == sizeof test<T>(0)) };
};
除了检查U::begin()
和U::end()
的存在外,还会检查它们是否返回可转换为const_iterator
的内容。它还避免了Stephan T. Lavavej通过使用必须支持的调用表达式突出显示的陷阱,而不是假定特定的签名。
[编辑] 对不起,这依赖于VC10的模板实例化。更好的方法(将存在性检查放在参数类型中,做参与重载):
template <typename T> struct is_container
{
// Is.
template <typename U>
static char test(U* u,
int (*b)[sizeof(typename U::const_iterator()==((U*)0)->begin())] = 0,
int (*e)[sizeof(typename U::const_iterator()==((U*)0)->end())] = 0);
// Is not.
template <typename U> static long test(...);
enum { value = (1 == sizeof test<T>(0)) };
};
答案 2 :(得分:3)
使用C ++ 11,现在有更好的方法来检测它。我们只是在表达式SFINAE上下文中调用它们,而不是依赖于函数的签名:
#include <type_traits> // declval
template<class T>
class is_container{
typedef char (&two)[2];
template<class U> // non-const
static auto test(typename U::iterator*, int)
-> decltype(std::declval<U>().begin(), char());
template<class U> // const
static auto test(typename U::const_iterator*, long)
-> decltype(std::declval<U const>().begin(), char());
template<class>
static two test(...);
public:
static bool const value = sizeof(test<T>(0, 0)) == 1;
};
Live example on Ideone. int
和long
参数仅用于在容器同时提供(或iterator
为typedef const_iterator iterator
时std::set
消除重载歧义,例如{{ 1}}允许) - 文字0
的类型为int
,并强制选择第一个重载。
答案 3 :(得分:1)
Stephan T. Lavavej this说:
请注意,技术上禁止使用标准库成员函数的地址。(它们可能会重载,使
&foo::bar
不明确,并且可以有其他默认参数,通过static_cast
打败消除歧义的企图。)
所以我想我将使用更简单的版本来检查嵌套的const_iterator
类型。
答案 4 :(得分:1)
这可能应该是评论,但我没有足够的分数
@MSalters
即使你的is_container
工作(差不多)而且我自己也使用了你的代码,我发现其中有两个问题。
首先,类型deque<T>::iterator
被检测为容器(在gcc-4.7中)。似乎deque<T>::iterator
已定义begin
/ end
个成员和const_iterator
类型。
第二个问题是根据GCC开发人员,此代码无效。我qoute:默认参数的值不是函数类型的一部分,不参与演绎。见GCC bug 51989
我目前正在使用this(仅限C ++ 11)is_container<T>
:
template <typename T>
struct is_container {
template <
typename U,
typename S = decltype (((U*)0)->size()),
typename I = typename U::const_iterator
>
static char test(U* u);
template <typename U> static long test(...);
enum { value = sizeof test<T>(0) == 1 };
};