6

我有一个很奇怪的问题

对于这样的代码

    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’)
4

5 回答 5

4

编译器在你的情况下选择第二个构造函数的原因A很简单:你是有符号类型10的值,而是一些无符号整数类型。这意味着必须将其转换为无符号整数类型。这种转换的需要使得第一个构造函数失去了对第二个构造函数的重载决议(这是完全匹配的)。您可以通过以下方式解决此问题intsize_type10InputIterator = int

A<int> a(10u, 10);

这消除了int -> unsigned转换的需要,并使第一个构造函数通过“非模板优于模板”子句赢得重载决议。

同时,它的工作方式不同的原因std::vector是语言规范对标准序列的构造函数给予了特殊处理。它只要求std::vector使用两个相同类型的整数作为参数的构造函数调用以某种方式“神奇地”解析为来自您的报价的第一个构造函数(即 size-and-initializer 构造函数)。每个具体实现如何实现这一点取决于实现。它可以重载所有整数类型的构造函数。它可以使用类似于 的功能enable_if。它甚至可以将其硬编码到编译器本身。等等。

例如,这就是 C++03 中的表述方式

23.1.1 序列

9对于本节和第 21 节中定义的每个序列:

— 构造函数

template <class InputIterator> 
X(InputIterator f, InputIterator l, const Allocator& a = Allocator()) 

应具有与以下相同的效果:

X(static_cast<typename X::size_type>(f),
  static_cast<typename X::value_type>(l), a) 

如果 InputIterator 是整数类型

C++11 更进一步,从不同的角度接近它,尽管意图保持不变:它声明如果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例如

    template <typename InputIterator>
    A(InputIterator first, InputIterator last, 
      typename std::enable_if<!std::is_integral<InputIterator>::value>::type* = 0)

您的编译器中是否有可用的 C++11 功能?

于 2013-06-06T16:59:32.043 回答
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太严格,则有更宽松的公式。例如,您可以保证两个参数都是迭代器。您可以走得更远(但不如上面的示例那么远)并确保它们“指向”可转换T(使用std::is_convertible)的类型。

于 2013-06-06T17:04:11.603 回答
0

您的第一个参数A<int>(10, 10)与您的显式构造函数不匹配,因为10已签名,因此它使用模板化构造函数。将其更改为A<int>(10u, 10),您最终可能会得到您期望的结果。

于 2013-06-06T17:04:08.780 回答
0

没错,模板化的东西更匹配,所以选择了。标准库实现为所有模板化成员的合理行为而努力。如果您想实现自己的类似集合,您可能需要查找一些专门化的实现代码。

或者你可能会找到一种方法来躲避这个问题。

有一篇很好的 GOTW 文章,其中包含函数重载选择的所有案例以及一些与之抗争的建议。

于 2013-06-06T17:01:46.767 回答
0

如果您正在编写一个通用库,您可能需要付出额外的努力,并使用模板元编程来捕获所有情况。或者简单地为所有整数类型提供显式重载。对于不太通用的使用,通常遵循以下规则就足够了,只要您为任何整数类型提供重载,您还提供一个 for int(因此A::A( int size, T const& initialValue = T() )除了您已经提供的那些之外,您还有一个构造函数)。

更一般地说:您可能应该只制作sizean int,然后完成它。标准库陷入了很多历史问题,并且必须size_t默认使用,但一般情况下,除非有非常强烈的理由不这样做,否则 C++ 中的正常整数类型是int; 此外,C++ 中的无符号类型具有非常奇怪的语义,只要有可能发生算术运算,就应该避免使用。

于 2013-06-06T17:32:09.020 回答