构造函数的优先级

Priorities of constructors c++

本文关键字:优先级 构造函数      更新时间:2023-10-16

我遇到了一个很奇怪的问题

下面的代码

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

编译器在A的情况下选择第二个构造函数的原因很简单:您的10signed类型int的值,而size_type是一些无符号整数类型。这意味着10必须被转换为无符号整数类型。这种转换的需要使第一个构造函数失去对第二个构造函数的重载解析(这与InputIterator = int完全匹配)。你可以通过

来解决这个问题
A<int> a(10u, 10);

这消除了int -> unsigned转换的需要,并使第一个构造函数通过"non-template优于template"条款。

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

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

<

23.1.1序列/strong>

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())
    { ... }

当然,这可能意味着您最终必须为所有整数类型添加重载。

我在上面已经提到的一个更好的解决方案是,通过使用enable_if或类似的基于sfinae的技术,禁用整数参数的模板构造函数。例如

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

你的编译器中有c++ 11的特性吗?

InputIteratorint时,实例化的

A(int first, int last)

比实例化的

更匹配
explicit A(unsigned int size = 0, const int &t = int())

,因为第一个参数是unsignedA<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)。

这是正确的,模板化的东西是一个更好的匹配,所以它被选中。标准库实现努力引导所有模板化成员的合理行为。如果您想实现自己的类似集合,可能需要查找一些专门化的实现代码。

或者你可以找到一个方法来回避这个问题。

GOTW上有一篇很好的文章,介绍了函数重载选择的所有情况和一些解决方法。

A<int>(10, 10)中的第一个参数与显式构造函数不匹配,因为10是有符号的,所以它使用模板化构造函数代替。将其更改为A<int>(10u, 10),您可能最终得到您期望的结果。

如果您正在编写一个通用库,您可能希望将额外的努力,并使用模板元编程来捕获所有的的病例。或者简单地为所有的类提供显式重载积分类型。对于不太通用的用法,通常就足够了要遵循这样的规则:任何时候为任何整数类型,您还为int提供了一个(因此您将拥有构造函数A::A( int size, T const& initialValue = T() );除了您已经提供的内容之外)。

更一般地说:你可能应该只让sizeint,并完成它。标准库赶上了在很多历史问题中,必须默认使用size_t,但一般来说,除非有非常强烈的理由否则,c++中标准的整型为int;在此外,c++中的无符号类型具有非常奇怪的语义,只要有算术的可能,就应该避免使用操作发生。