我遇到了非常奇怪的问题
代码如此
template <typename T>
struct A{
explicit A(unsigned int size = 0, const T &t = T())
{
}
template <typename InputIterator>
A(InputIterator first, InputIterator last) {
for(;first != last ; ++first)
{
*first; //do something with iterator
}
}
};
当我例如定义
时 A<int> a(10,10);
使用迭代器的第二个构造函数而不是第一个构造函数。 当它们看起来相似时,载体构造函数如何工作?
explicit vector (size_type n, const value_type& val = value_type(),
const allocator_type& alloc = allocator_type());
template <class InputIterator>
vector (InputIterator first, InputIterator last,
const allocator_type& alloc = allocator_type());
我可以毫无困难地制作矢量v(10,10)。
PS我有这样的错误
temp.cpp: In instantiation of ‘A<T>::A(InputIterator, InputIterator) [with = int; T = int]’:
temp.cpp:17:15: required from here
temp.cpp:12:4: error: invalid type argument of unary ‘*’ (have ‘int’)
答案 0 :(得分:4)
编译器在A
的情况下选择第二个构造函数的原因很简单:您的10
值为已签名类型int
,而{ {1}}是一些无符号整数类型。这意味着size_type
必须转换到无符号整数类型。转换的需要使第一个构造函数失去第二个构造函数的重载决策(这与10
完全匹配)。您可以通过
InputIterator = int
这消除了A<int> a(10u, 10);
转换的需要,并使第一个构造函数通过“非模板优于模板”子句赢得重载解析。
同时,它与int -> unsigned
的工作方式不同的原因是语言规范为标准序列的构造函数提供了特殊处理。它只是需要 std::vector
构造函数调用与两个与参数相同类型的整数以某种方式“神奇地”解析为你的引用中的第一个构造函数(即size-and-initializer构造函数) 。每种具体实施如何实现取决于实施。它可以为所有整数类型重载构造函数。它可以使用类似于std::vector
的功能。它甚至可以将其硬编码到编译器本身。等等。
这就是C ++ 03中的陈述,例如
23.1.1序列
9 对于本条款和第21条中定义的每个序列:
- 构造函数
enable_if
具有与以下相同的效果:
template <class InputIterator>
X(InputIterator f, InputIterator l, const Allocator& a = Allocator())
如果InputIterator是整数类型
C ++ 11,通过从不同的角度处理它,甚至更进一步,尽管意图保持不变:它声明如果X(static_cast<typename X::size_type>(f),
static_cast<typename X::value_type>(l), a)
不符合输入迭代器,则应排除模板构造函数从重载决议。
因此,如果您希望类模板InputIterator
的行为与A
的行为方式相同,则必须以这种方式刻意设计。实际上,您可以查看平台上的标准库实现,以了解它们如何为std::vector
执行此操作。
无论如何,一个低技术的暴力解决方案是为std::vector
参数添加一个专用的重载构造函数
int
当然,这可能意味着您最终必须为所有整数类型添加重载。
我在上面已经提到过的一个更好的解决方案是使用 explicit A(unsigned int size = 0, const T &t = T())
{ ... }
explicit A(int size = 0, const T &t = T())
{ ... }
或类似的基于SFINAE的技术禁用整数参数的模板构造函数。例如
enable_if
您的编译器中是否有可用的C ++ 11功能?
答案 1 :(得分:2)
当InputIterator
为int
时,实例化
A(int first, int last)
是比实例化的
更好的匹配explicit A(unsigned int size = 0, const int &t = int())
由于第一个参数为unsigned
。 A<int> a((unsigned int)10,10)
应调用您期望的构造函数。您还可以使用SFINAE来阻止匹配,除非构造函数确实将两个迭代器传递给T
:
#include <iostream>
using namespace std;
template <typename T>
struct A{
explicit A(unsigned int size = 0, const T &t = T())
{
cout << "size constructor for struct A" << endl;
}
template <class I>
using is_T_iterator = typename enable_if<is_same<typename iterator_traits<I>::value_type, T>::value, T>::type;
template <typename InputIterator>
A(InputIterator first, InputIterator last, is_T_iterator<InputIterator> = 0) {
cout << "iterator constructor for struct A" << endl;
for(;first != last ; ++first)
{
*first; //do something with iterator
}
}
};
int main()
{
A<int>(10,10);
A<int>((int*)0,(int*)0);
//A<int>((char*)0,(char*)0); //<-- would cause compile time error since (char *) doesn't dereference to int
return 0;
}
如果两个参数都是T
的迭代器的条件太严格,那么有更宽松的公式。例如,您可以保证两个参数都是迭代器。你可以走得更远(但不如上面的例子那么远)并确保它们“指向” convertible 到T
的类型(使用std::is_convertible
)。
答案 2 :(得分:0)
这是正确的,模板化的东西是更好的匹配,所以它被选中。标准库实现难以引导所有模板化成员的明智行为。如果要实现自己的类似集合,可能需要查找一些特殊化的实现代码。
或者你可能找到一种方法来避免这个问题。
有一篇很好的GOTW文章,其中包含所有功能过载选择的案例以及一些建议。
答案 3 :(得分:0)
A<int>(10, 10)
中的第一个参数与显式构造函数不匹配,因为10
已签名,因此它使用的是模板化构造函数。将其更改为A<int>(10u, 10)
,您可能会得到您期望的结果。
答案 4 :(得分:0)
如果你正在写一个通用的图书馆,你可能想要写一个
额外的努力,并使用模板元编程来捕捉所有
案件。或者只是为所有人提供显式重载
整体类型。对于不太通用的用途,通常就足够了
遵循任何时候你为任何人提供过载的规则
整数类型,你也提供一个int
(所以你会有
构造函数A::A( int size, T const& initialValue = T() )
,
除了你已经提供的那些)。
更一般地说:你应该只是让size
成为一个
int
,并完成它。标准库被赶上了
在许多历史问题中,默认情况下必须使用size_t
,
但总的来说,除非有很强的理由要做
否则,C ++中的常规整数类型为int
;在
另外,C ++中的无符号类型具有非常奇怪的语义,
并且应该在任何有可能算术的时候避免
发生的操作。