Неконстантный конструктор копирования и неявные преобразования возвращаемого значения

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

struct B { };
struct A
{
        A(int);
        A(A&); // missing const is intentional
        A(B);
        operator B();
};

A f()
{
        // return A(1); // compiles fine
        return 1; // doesn't compile
}

Это прекрасно компилируется на MSVC++ 2010 (на самом деле, на MSVC это даже работает, если я удаляю B в целом). Это не на GCC 4.6.0:

conv.cpp: In function ‘A f()’:
conv.cpp:13:9: error: no matching function for call to ‘A::A(A)’
conv.cpp:13:9: note: candidates are:
conv.cpp:6:2: note: A::A(B)
conv.cpp:6:2: note:   no known conversion for argument 1 from ‘A’ to ‘B’
conv.cpp:5:2: note: A::A(A&)
conv.cpp:5:2: note:   no known conversion for argument 1 from ‘A’ to ‘A&’
conv.cpp:4:2: note: A::A(int)
conv.cpp:4:2: note:   no known conversion for argument 1 from ‘A’ to ‘int’

Что меня смущает, так это сообщение no known conversion for argument 1 from ‘A’ to ‘B’, Как это может быть правдой, учитывая, что A::operator B() очень хорошо определен?

4 ответа

Решение

Ошибка вполне очевидна в списке кандидатов, которые были отклонены. Проблема заключается в том, что неявные последовательности преобразования, включающие пользовательское преобразование на языке C++, ограничены одним пользовательским преобразованием:

§13.3.3.1.2 [over.ics.user] / 1 Пользовательская последовательность преобразования состоит из начальной стандартной последовательности преобразования, за которой следует пользовательское преобразование (12.3), за которым следует вторая стандартная последовательность преобразования.

Стандартные последовательности преобразования определены в §4 [conv]:

[...] Стандартная последовательность преобразований - это последовательность стандартных преобразований в следующем порядке

  • Нулевое или одно преобразование из следующего набора: преобразование lvalue-в-значение, преобразование массива в указатель и преобразование функции в указатель.

  • Ноль или одно преобразование из следующего набора: интегральные преобразования, повышение с плавающей запятой, интегральные преобразования, преобразования с плавающей запятой, преобразования с плавающей запятой, преобразования указателя, преобразования указателя в член и логические преобразования.

  • Ноль или одна квалификация конверсии.

Проблема в том, что ваш код не может получить из пункта а) int значение до пункта б) B применяя одно пользовательское преобразование.

В частности, все доступные последовательности преобразования начинаются с определенного пользователем преобразования (неявный конструктор A(int)) которые дают A Rvalue. Оттуда, rvalue не может быть привязано к неконстантной ссылке на вызов A::A( A& ), так что этот путь отбрасывается. Все остальные пути требуют второго определенного пользователем преобразования, которое недопустимо, и фактически единственный другой путь, который приведет нас к пункту b), требует двух других определенных пользователем преобразований в общей сложности 3.

Потому что вы не можете сделать более одного неявного преобразования. Вы должны были бы пойти A::A(A::A(int)::operator B()) чтобы заставить это работать, и это слишком много шагов, чтобы компилятор мог сам разобраться.

Я не думаю, что причина в том, что "слишком много шагов для самостоятельной оценки", как указал DeadMG. У меня были конструкции с 3-4 преобразованиями, и компилятор всегда отлично их понимал.

Я считаю, что проблема скорее в том, что компилятору не разрешено преобразовывать const ссылка на неconstссылка от своего имени (это разрешено делать только в том случае, если вы явно указали это с помощью приведения).
А поскольку ссылка на временный объект, который передается конструктору копирования, const, но конструктор копирования - нет, он не находит подходящей функции.

РЕДАКТИРОВАТЬ: Я не нашел никакого "реального" кода (см. Комментарии ниже), но создал пример мультизигзагообразного преобразования, который фактически компилируется без ошибок в gcc 4.5. Обратите внимание, что это прекрасно скомпилируется с -Wall -Wextra тоже, что меня откровенно удивляет.

struct B
{
    signed int v;
    B(unsigned short in) : v(in){}
};

struct C
{
    char v;
    C(int in) : v(in){}
};

struct A
{
    int v;
    A(B const& in) : v(in.v){}
    operator C() { return C(*this); }
};

enum X{ x = 1 };

int main()
{
    C c = A(x);
    return 0;
}

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

Другие вопросы по тегам