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

发布于 2024-08-17 06:29:50 字数 800 浏览 6 评论 0原文

考虑以下代码:

#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());
}

上面的代码应该编译吗?我的感觉是不应该,因为构造函数被标记为“显式”。

Microsoft Visual C++ 同意这一点,并给出了明确的错误消息:无法从 'int' 转换为 'const A';结构“A”的构造函数被声明为“显式”

但是,使用 Comeau 的在线编译器,代码编译成功。

哪个是正确的?

编辑:

有趣的是,将vector更改为set(在将运算符<添加到A之后)会导致两个编译器给出一个错误。

但是,将 vector 更改为 map 并将 vector 更改为 map 导致两个编译器都接受代码!

Consider the following code:

#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());
}

Should the above compile? My feeling is that it should not, due to the constructor being marked explicit.

Microsoft Visual C++ agrees, giving a clear error message: cannot convert from 'int' to 'const A'; Constructor for struct 'A' is declared 'explicit'

However, using Comeau's online compiler, the code compiles successfully.

Which is correct?

Edit:

Interestingly, changing vector to set (after adding an operator < to A) causes both compilers to give an error.

However, changing vector<int> to map<int, int> and vector<A> to map<A, A> causes both compilers to accept the code!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

美煞众生 2024-08-24 06:29:50

我查看了 GCC 的 STL 实现,它应该有类似的行为。原因如下。

  • vector 的元素由通用函数模板初始化,该模板接受任意两种类型 XV 并调用 new( p ) X ( v ) 其中 v 是一个 V (我解释了一下)。这允许显式转换。
  • setmap 的元素由 _tree 的私有成员函数初始化,该函数特别期望 T const & 传入。此成员函数不是模板(超出了模板的成员范围),因此如果初始值无法隐式转换为 T ,调用失败。 (我再次简化了代码。)

该标准不要求在初始化具有范围的容器时进行显式转换工作或隐式转换不起作用。它只是说范围被复制到容器中。对于您的目的来说绝对不明确。

令人惊讶的是,存在这样的歧义,考虑到他们已经如何考虑到诸如 几周前我拥有的那个

I looked through GCC's STL implementation and it should have similar behavior. Here's why.

  • Elements of a vector are initialized by a generic function template which accepts any two types X and V and calls new( p ) X( v ) where v is a V (I'm paraphrasing a bit). This allows explicit conversion.
  • Elements of a set or map are initialized by a private member function of _tree<T,…> which specifically expects a T const & to be passed in. This member function isn't a template (beyond being a member of a template), so if the initial value can't be implicitly converted to T, the call fails. (Again I'm simplifying the code.)

The standard doesn't require that explicit conversion work or that implicit conversion not work when initializing a container with a range. It simply says that the range is copied into the container. Definitely ambiguous for your purpose.

Surprising such ambiguity exists, considering how they've already refined the standard in consideration of problems like the one I had a couple weeks ago.

浮云落日 2024-08-24 06:29:50

I think it would depend on how std::vector<A> As(Iterator,Iterator) is implemented in your particular implementation of the STL.

浅黛梨妆こ 2024-08-24 06:29:50

这是一个相当棘手的问题,可能 VisualStudio 是对的,而 Comeau 是错的(这看起来真的很难相信)。

标准如果逐字阅读,则根据复制构造函数定义向量构造函数(请参阅引用),这字面意思是通过取消引用迭代器获得的对象必须首先转换为类型 T然后应该调用复制构造函数。此时,使用显式构造函数,代码不应编译。

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

我不认为任何不使用 Comeau 方法的合理论点,我相信(这只是个人意见)标准中关于向量构造函数复杂性的措辞可能应该重述为只需要 N 次调用适当的 T 构造函数,在适当的情况下,必须将其定义为与调用 T( *first ) 匹配的构造函数(即,采用 InputIterator:: 的构造函数: value_type(通过值或可能的常量引用),或从 InputIterator::value_type 到 T 隐式转换后的 T 复制构造函数

。23.2.4.1 [lib.vector.cons]/1

复杂性:构造函数模板向量(InputIterator
首先,InputIterator 最后)仅使
N 次调用 T 的复制构造函数
(其中 N 是之间的距离
第一个和最后一个)并且没有重新分配
如果迭代器的第一个和最后一个是
正向、双向或随机
访问类别。它使订单N
调用 T 的复制构造函数和
排序日志 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 ,而是构造 v1 中的元素直接来自 v2 中的元素。我假设使用 VS,编译器将使用 T2::operator T1v2 中的每个元素转换为 T1 元素,然后调用复制构造函数。是这样吗?

This is a rather tricky question, and it might be the case that VisualStudio is right and Comeau wrong (this seems really hard to believe).

The standard if read word by word, defines that vector constructor in terms of the copy constructor (see quote), and that literally means that the object obtained by dereferencing the iterator must first be converted into the type T and then the copy constructor should be called. At this point, with an explicit constructor the code should not compile.

It seems reasonable to expect an implementation, on the other hand, to directly call a constructor taking as argument the dereferenced iterator, in which case the constructor call would be explicit and thus the code should compile. This would go against the exact wording in the quote below, as the copy constructor is defined for a given type T as a constructor that taking a single possibly constant reference to an object of type T.

I cannot think of any reasonable argument not to use the Comeau approach, and my believe (this is just personal opinion) is that the wording in the standard with respect to the complexity of the vector constructor should probably be restated as requiring only N calls to the appropriate T constructor, where appropriate would have to be defined as the constructor that matches the call T( *first ) (that is, either a constructor taking an InputIterator::value_type (by value or possibly constant reference), or the T copy constructor after an implicit conversion from InputIterator::value_type to T.

23.2.4.1 [lib.vector.cons]/1

Complexity: The constructor template vector(InputIterator
first, InputIterator last) makes only
N calls to the copy constructor of T
(where N is the distance between
first and last) and no reallocations
if iterators first and last are of
forward, bidirectional, or random
access categories. It makes order N
calls to the copy constructor of T and
order log N reallocations if they are
just input iterators.

I would like to know how the VS compiler behaves when given:

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() );
}

With g++ the result is that T2::operator T1 is not called, but rather the elements in v1 are constructed directly from the elements in v2. I would assume that with VS the compiler would use T2::operator T1 to convert from each element in v2 to a T1 element and then call the copy constructor. Is that so?

凡尘雨 2024-08-24 06:29:50

这实际上归结为STL库如何实现的问题,而不是语言规范的问题。语言规范中没有任何内容会阻止它工作,也没有任何内容要求它工作。

如果编写 stl::vector 构造函数来尝试使用赋值运算符进行隐式转换,那么它将失败。 Microsoft STL 实现更有可能在初始化期间通过构造函数调用使用返回值优化,在这种情况下,此代码可以正常工作。

值得注意的是,它起作用的唯一原因是因为 stl::vector 构造函数是模板化的,唯一的要求是它是一个 input_iterator,或者更准确地说,它支持输入迭代器的所有必需功能。

我还想指出,这是一个很好的例子,说明了为什么编写跨平台代码通常很困难。有时,您最终会遇到这样的问题:两个编译器都不一定偏离语言标准,但代码仍然不可移植。

This really boils down to a question of how the STL library is implemented, not a language specification issue. There is nothing in the language spec that would prohibit this from working, nor is there anything that would require that it should work.

If the stl::vector constructor were written to try an implicit conversion using the assignment operator, then it would fail. It is more likely that the Microsoft STL implementation makes use of return-value optimization during initialization via a constructor call, in which case this code would work fine.

It is important to note that the only reason this works is because the stl::vector constructor is templated, and the only requirement is that it is an input_iterator, or more accurately that it supports all the required functionality of an input iterator.

I'd also like to point out that this is a prime example of why it is often difficult to write cross-platform code. Sometimes you end up with issues where neither compiler necessarily deviates from the language standard, but code still isn't portable.

ㄖ落Θ余辉 2024-08-24 06:29:50

此代码无法在 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

This code does not compile in Comeau:

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

class Bar
{
 void DoStuff(Foo foo){

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

Error message:

"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".

So at the rudimentary level the online compiler supports explicit constructors. Must be something to do with the vector/iterators.

EDIT This however compiles:

Foo foo = (Foo)5;

Which is an explicit conversion, so that's OK. My guess the Comeau vector class does an explicit cast in the constructor somewhere, where's Microsoft's library doesn't.

More on explicit constructors - http://www.glenmccl.com/tip_023.htm

番薯 2024-08-24 06:29:50

是的,它应该编译。如果不使用构造函数,那么它的显式性就不是问题。

Yes it should compile. If the constructor is not used, then its explicitness is not an issue.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文