C++显式构造函数和迭代器

8
请看下面的代码:

考虑以下代码:

#include <vector>

struct A
{
    explicit A(int i_) : i(i_) {}
    int i;
};

int main()
{
    std::vector<int> ints;
    std::vector<A> As(ints.begin(), ints.end());
}

以上代码是否能够编译?我的感觉是不行的,因为构造函数被标记为explicit

Microsoft Visual C++ 给出了明确的错误信息:cannot convert from 'int' to 'const A'; Constructor for struct 'A' is declared 'explicit'

然而,在使用Comeau在线编译器时,代码成功编译。

哪个是正确的呢?

编辑:

有趣的是,将vector更改为set(在 A 中添加operator <后),两个编译器都会报错。

但是,将vector<int>更改为map<int, int>,并将vector<A>更改为map<A, A>,这两个编译器都接受该代码!


间接回答在这里:http://stackoverflow.com/questions/1943228/implicit-constructor-conversion-works-on-explicit-vectorvector-only-sometimes - Drew Dormann
1
我刚刚根据那个问题+答案写了一个回答,但它实际上并不适用,因为 std::vector<int>::iterator 不是整数类型,而且 fill-constructor 没有被调用。所以我删除了它。我认为我能说的就是,在任何容器构造函数中,标准都不会禁止显式转换。 - Potatoswatter
VS 2010 Beta的行为类似于Comeau和gcc。 - David Rodríguez - dribeas
6个回答

4

我查看了GCC的STL实现,它应该具有相似的行为。以下是原因。

  • vector中的元素由一个通用函数模板初始化,其接受任何两个类型XV并调用new(p)X(v),其中v是一个V(这里有点改述)。这允许显式转换。
  • setmap的元素是由_tree<T,…>的私有成员函数初始化的,该函数专门期望传递T const &。如果该初始值无法隐式转换为T,则该调用将失败。(再次说明了代码简化的内容。)

标准不要求在使用范围初始化容器时显式转换可用或隐式转换不可用。它只是说范围被复制到容器中。对于你的目的来说,这肯定是有歧义的。

令人惊讶的是,存在这样的歧义,考虑到他们已经考虑了像我几周前遇到的问题这样的问题。


1

这是一个相当棘手的问题,可能是VisualStudio正确而Comeau错误(这似乎很难相信)。

如果逐字逐句地阅读标准,它会用复制构造函数(见引用)来定义向量构造函数,这意味着通过解引用迭代器获得的对象必须首先转换为类型T,然后应调用复制构造函数。此时,使用显式构造函数代码不应编译。

另一方面,可以合理地期望实现直接调用以解引用的迭代器作为参数的构造函数,在这种情况下,构造函数调用将是显式的,因此代码应该编译。这将违反以下引文中的确切措辞,因为复制构造函数被定义为对于给定类型T,它是一个取单个可能是常量引用的类型T对象的构造函数。

我想不出任何合理的理由不使用Comeau方法,我的信念(这只是个人意见)是标准中关于向量构造函数复杂性的措辞可能应该重新表述为要求仅调用适当的T构造函数N次,其中适当需要定义为与调用T(*first)匹配的构造函数(即,通过值或可能的常量引用获取InputIterator::value_type的构造函数,或在从InputIterator::value_type到T的隐式转换后使用T复制构造函数)。

23.2.4.1 [lib.vector.cons]/1

复杂度:构造函数模板vector(InputIterator first, InputIterator last)仅对T的复制构造函数进行N次调用(其中N是first和last之间的距离),如果迭代器first和last属于前向、双向或随机访问类别,则不进行任何重新分配。如果它们只是输入迭代器,则进行N次T的复制构造函数调用和log N次重新分配。

我想知道当给出以下内容时,VS编译器的行为如何:

struct T1;
struct T2 {
   operator T1 ();
};
struct T1 {
   T1( T2 const & ) { std::cout << "T1(T2)" << std::endl; }
};
T2::operator T1() {
   std::cout << "T2::operator T1" << std::endl;
   return T1(*this);
}
int main() {
   std::vector<T2> v2;
   v2.push_back( T2() );
   std::vector<T1> v1( v2.begin(), v2.end() );
}

使用g++编译器的结果是,不会调用T2::operator T1,而是直接从v2中的元素构造出v1中的元素。我认为,使用VS编译器时,编译器应该会使用T2::operator T1v2中的每个元素转换为T1元素,然后再调用拷贝构造函数。是这样吗?


事实上,尝试使用VS编译器编译您的代码会出现错误(C2664):'std::allocator<_Ty>::construct':无法将参数2从'T2'转换为'const T1&',其中[_Ty = T1]原因:无法将'T2'转换为'const T1',没有可执行此转换的用户定义的转换运算符,或者该运算符无法调用。 - user200783
我刚刚使用VS2010 beta进行了测试,它不仅编译通过,而且执行时表现与g++相同:会调用T1(T2 const&)构造函数。 - David Rodríguez - dribeas

1
我认为这取决于你在STL的具体实现中如何实现std::vector<A> As(Iterator, Iterator)

标准库的行为不应该依赖于实现。 - anon
1
然而,也有一些地方(非常罕见)标准可以被解释成两种不等效的方式。 - David Rodríguez - dribeas

1
这实际上是一个关于STL库如何实现的问题,而不是语言规范问题。语言规范中没有任何禁止此功能工作的内容,也没有任何要求它应该工作的内容。
如果stl::vector构造函数尝试使用赋值运算符进行隐式转换,则会失败。更可能的是,Microsoft STL实现在初始化时通过构造函数调用使用返回值优化,这种情况下,此代码将正常工作。
需要注意的是,这仅能够工作是因为stl::vector构造函数是模板化的,并且唯一的要求是它是一个输入迭代器,或者更准确地说,它支持所有所需的输入迭代器功能。
我还想指出,这是为什么编写跨平台代码通常很困难的一个典型例子。有时候你会遇到问题,其中两个编译器都不一定偏离语言标准,但代码仍然无法移植。

0

这段代码在Comeau中无法编译:

class Foo
{
public:
 explicit Foo(int bar)
 {
 }
};

class Bar
{
 void DoStuff(Foo foo){

 }
 void DoStuff2()
 {
  DoStuff(4);
 }
};

错误信息:

"ComeauTest.c", line 16: error: no suitable constructor exists to convert from "int"
          to "Foo"
    DoStuff(4);
            ^

1 error detected in the compilation of "ComeauTest.c".

因此,在基本水平上,在线编译器支持显式构造函数。这可能与向量/迭代器有关。

编辑 然而,这个可以编译:

Foo foo = (Foo)5;

这是一个显式转换,所以没问题。我猜Comeau向量类在构造函数中进行了显式转换,而Microsoft的库没有。

关于显式构造函数的更多信息- http://www.glenmccl.com/tip_023.htm


0

是的,它应该能编译通过。如果没有使用构造函数,则其明确性不是问题。


如果构造函数没有被使用,我们还可以通过什么方式将int转换为A? - user200783
没有任何东西被创建,也没有任何东西被转换 - 两个向量都是空的。 - anon
如果在构建"As"之前向"ints"向量中插入了某些内容,则行为相同。 - user200783

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接