C ++ явные конструкторы и итераторы - PullRequest
7 голосов
/ 05 января 2010

Рассмотрим следующий код:

#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 , код успешно компилируется.

Что правильно?

Edit:

Интересно, что изменение vector на set (после добавления operator < в A) приводит к тому, что оба компилятора выдают ошибку.

Однако, изменение vector<int> на map<int, int> и vector<A> на map<A, A> заставляет оба компилятора принимать код!

Ответы [ 6 ]

3 голосов
/ 05 января 2010

Я просмотрел реализацию STL в GCC, и она должна иметь похожее поведение. И вот почему.

  • Элементы vector инициализируются шаблоном универсальной функции, который принимает любые два типа X и V и вызывает new( p ) X( v ), где v - это V (я перефразирую немного) , Это позволяет явное преобразование.
  • Элементы set или map инициализируются закрытой функцией-членом _tree<T,…>, которая определенно ожидает передачи T const &. Эта функция-член не является шаблоном (кроме того, что она является членом шаблон), поэтому, если начальное значение не может быть неявно преобразовано в T, вызов завершится неудачно. (Опять я упрощаю код.)

Стандарт не требует, чтобы явное преобразование работало или чтобы неявное преобразование не работало при инициализации контейнера с диапазоном. Это просто говорит о том, что диапазон копируется в контейнер. Определенно неоднозначно для вашей цели.

Удивительно, что такая двусмысленность существует, если учесть, как они уже уточнили стандарт с учетом таких проблем, как та, которая у меня была пару недель назад.

1 голос
/ 05 января 2010

Это действительно сводится к вопросу о том, как реализована библиотека STL, а не к вопросу спецификации языка. В спецификации языка нет ничего, что могло бы запретить это, и нет ничего, что требовало бы его работы.

Если бы конструктор stl :: vector был написан для попытки неявного преобразования с использованием оператора присваивания, то он потерпел бы неудачу. Более вероятно, что реализация Microsoft STL использует оптимизацию возвращаемого значения во время инициализации с помощью вызова конструктора, и в этом случае этот код будет работать нормально.

Важно отметить, что единственная причина, по которой это работает, заключается в том, что конструктор stl :: vector является шаблонным, а единственное требование - это то, что он является input_iterator, или, точнее, он поддерживает все необходимые функциональные возможности итератора ввода. .

Я также хотел бы отметить, что это яркий пример того, почему часто бывает трудно писать кроссплатформенный код. Иногда вы сталкиваетесь с проблемами, когда ни один компилятор не обязательно отклоняется от языкового стандарта, но код все еще не переносим.

1 голос
/ 05 января 2010

Это довольно сложный вопрос, и, возможно, дело в том, что VisualStudio прав, а Comeau неправ (в это, кажется, трудно поверить).

Стандарт, читаемый слово за словом, определяет этот векторный конструктор в терминах конструктора копирования (см. Цитату), и это буквально означает, что объект, полученный путем разыменования итератора, должен сначала быть преобразован в введите T, а затем должен быть вызван конструктор копирования. На этом этапе с явным конструктором код не должен компилироваться.

Представляется разумным ожидать, что реализация, с другой стороны, будет напрямую вызывать конструктор, принимая в качестве аргумента разыменованный итератор, и в этом случае вызов конструктора будет явным, и, следовательно, код должен компилироваться. Это пошло бы против точной формулировки в приведенной ниже цитате, так как конструктор копирования определен для данного типа T как конструктор, который принимает одну, возможно, постоянную ссылку на объект типа T.

Я не могу придумать ни одного разумного аргумента, чтобы не использовать подход Comeau, и я считаю (это всего лишь личное мнение), что формулировка в стандарте относительно сложности векторного конструктора, вероятно, должна быть переформулирована как требующая только N обращений к соответствующему T-конструктору , где это необходимо, должно быть определено как конструктор, который соответствует вызову T( *first ) (то есть либо конструктор, принимающий InputIterator::value_type (по значению, либо, возможно, постоянная ссылка ) или конструктор T-копии после неявного преобразования из InputIterator::value_type в 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 T1 для преобразования из каждого элемента в v2 в элемент T1, а затем вызвать конструктор копирования. Это так?

1 голос
/ 05 января 2010

Я думаю, это будет зависеть от того, как std::vector<A> As(Iterator,Iterator) реализовано в вашей конкретной реализации STL.

0 голосов
/ 05 января 2010

Да, это должно скомпилироваться. Если конструктор не используется, то его явность не является проблемой.

0 голосов
/ 05 января 2010

Этот код не компилируется в 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

...