Почему (удаленный) конструктор копирования предпочтительнее неявного преобразования?

Рассмотрим код ниже:

struct Bar{};

struct Foo
{
    Foo() = default;
    Foo(const Bar&) {}
    Foo(const Foo&) = delete;

    // IMPLICIT conversion to Bar
    operator Bar(){return {};}
};

int main() 
{
    Foo f1;
    Foo f2(static_cast<Bar>(f1)); // this is OK
    Foo f3(f1); // does not compile, why not implicit conversion to `Bar`?
}

Класс Bar имеет определяемый пользователем оператор преобразования в Foo, который принимает Bar&s. Однако в последней строке main я ожидал, что Foo f1 будет преобразовано в Bar, а затем передано в Foo(const Bar&). Однако рассматривается только удаленный конструктор Foo(const Foo&) = delete;. Я понимаю, что этот конструктор лучше подходит, но почему Foo(const Bar&) также не входит в набор перегрузки и почему компилятор не выполняет неявное преобразование?


person vsoftco    schedule 18.11.2015    source источник


Ответы (1)


Это предпочтительнее, поскольку поиск и разрешение перегрузки происходят до того, как удаленное определение будет отмечено в удаленной функции-члене.

То есть разрешение перегрузки не будет учитывать спецификатор delete и в вашем вызове:

Foo f3(f1);

Из-за того, что f1 относится к типу Foo, Foo(const Foo&) является прямым совпадением типа параметра. Следовательно, имеет более высокий ранг в разрешении перегрузки, чем Foo(const Bar&).

person 101010    schedule 18.11.2015
comment
Да, но если у вас есть 2 одинаково хороших кандидата, и один из них deleted, я бы ожидал, что компилятор выберет другого. Любая причина, по которой этого не происходит? - person vsoftco; 19.11.2015
comment
@vsoftco delete больше похоже на то, что вызов этого не удастся, это не значит, что его не существует. - person 101010; 19.11.2015
comment
Ожидалось, что у меня будет поведение, похожее на SFINAE, где несовпадающая функция просто игнорируется, если есть другое совпадение. - person vsoftco; 19.11.2015
comment
@vsoftco Похоже, вы пытаетесь использовать удаленные функции способом, который существенно отличается от того, как их определяет С++. То, что вам нужно, имеет смысл, это просто не то, как работает С++. Учитывая, например. void f(long); void f(int) = delete;, f(1) не будут работать, а void f(int) = delete; вполне может быть добавлено специально, чтобы f(1) не работало. Такие удаленные перегрузки есть в стандартной библиотеке. По правилам, которые вы хотели бы, f(1) будет работать, он просто выберет перегрузку void f(long). - person ; 19.11.2015
comment
@Т.С. Ах, хорошо, это имеет смысл, поэтому удаленный имеет более высокий ранг. Спасибо. - person vsoftco; 19.11.2015
comment
Это кристально ясно, спасибо. Есть ли способ удалить функцию из набора кандидатов на перегрузку? (это то, что я сначала делаю, хотя =delete) - person vsoftco; 19.11.2015
comment
@vsoftco Нет, это невозможно сделать. Я предлагаю придерживаться static_cast таким образом, вы также покажете свои намерения тому, кто читает код. - person 101010; 19.11.2015
comment
Спасибо, вы правы. Я столкнулся с этими проблемами при написании класса для матричных представлений (ленивая оценка), где я хотел отключить временные объекты и т. д. Но я думаю, что мне нужно подумать об изменении дизайна;) - person vsoftco; 19.11.2015
comment
@M.M Да, но в этом случае он будет привязан к конструктору копирования по умолчанию. - person vsoftco; 19.11.2015
comment
@MM Не будет ли в этом случае компилятор определять для вас неявный конструктор копирования? - person 101010; 19.11.2015
comment
@ 101010 да, я на мгновение подумал, что он был подавлен по какой-то причине - person M.M; 19.11.2015
comment
@M.M Мне любопытно, что произойдет, если я удалю перемещение ctor, что отключит сгенерированный компилятором copy ctor... - person vsoftco; 19.11.2015
comment
@vsoftco Если вы удаляете копирующий ctor перемещения, ctor также неявно объявляется удаленным. - person 101010; 19.11.2015
comment
Я бы предположил, что то, что вы хотите, невозможно по уважительной причине: инициализация T из объекта типа T всегда должна быть копирующей конструкцией. Вы могли бы, конечно, написать Foo(const Foo&f): Foo(static_cast<Bar>(f)) {} - person M.M; 19.11.2015
comment
@ 101010 Да, это точно такой же эффект, как и явное =delete. - person vsoftco; 19.11.2015
comment
@vsoftco, согласно cppreference, невозможно подавить все конструкторы копирования - person M.M; 19.11.2015
comment
@M.M Да, я согласен, поэтому я должен изменить дизайн. - person vsoftco; 19.11.2015
comment
@ М.М. Что ты имеешь в виду? удаление конструктора перемещения также удаляет ctor копирования. - person vsoftco; 19.11.2015
comment
@vsoftco, то он определяется как удаленный (в отличие от того, что он вообще не определен), что имеет значение для разрешения перегрузки. - person M.M; 19.11.2015