неправильное преобразование аргумента предпочтительно при вызове функции

Пишу программу под MS Visual C++ 6.0 (да, я знаю, что она древняя, нет ничего не могу обновить). Я вижу какое-то поведение, которое я считаю действительно странным. У меня есть класс с двумя конструкторами, определенными следующим образом:

class MyClass
{
public:
    explicit MyClass(bool bAbsolute = true, bool bLocation = false) : m_bAbsolute(bAbsolute), m_bLocation(bLocation) { ; }
    MyClass(const RWCString& strPath, bool bLocation = false);

private:
    bool m_bAbsolute;
    bool m_bLocation;
};

Когда я создаю экземпляр этого класса с таким синтаксисом: MyClass("blah"), он вызывает первый конструктор. Как вы видите, я добавил к нему ключевое слово explicit в надежде, что он этого не сделает... нет костей. Казалось бы, преобразование из const char * в bool предпочтительнее преобразования в RWCString, у которого есть конструктор копирования, который принимает const char *. Почему это происходит? Я бы предположил, что с учетом двух возможных вариантов, подобных этому, он бы сказал, что это неоднозначно. Что я могу сделать, чтобы предотвратить это? Если это вообще возможно, я бы хотел избежать явного приведения аргумента strPath к RWCString, так как он будет часто использоваться с литералами, а это много лишнего ввода (плюс действительно легко сделать ошибку).


person rmeador    schedule 19.03.2009    source источник


Ответы (7)


Явное здесь не поможет, так как конструктор не является частью неявного преобразования, а только получателем.

Невозможно контролировать предпочтительный порядок преобразований, но вы можете добавить второй конструктор, который принимает const char*. Например:

class MyClass
{
public:
    MyClass(bool bAbsolute = true, bool bLocation = false);
    MyClass(const RWCString& strPath, bool bLocation = false);
    MyClass(const char* strPath, bool bLocation = false);

private:
    bool m_bAbsolute;
    bool m_bLocation;
};
person Andrew Grant    schedule 19.03.2009

Эндрю Грант предложил решение. Я хочу рассказать вам, почему это не работает так, как вы пытались. Если у вас есть две жизнеспособные функции для аргумента, то вызывается та, которая лучше всего соответствует аргументу. Для второго требуется пользовательское преобразование, а для первого требуется только стандартное преобразование. Вот почему компилятор предпочитает первое второму.

person Johannes Schaub - litb    schedule 19.03.2009

Если вы не хотите продолжать кастовать его, то мне кажется, что вам, возможно, придется создать еще один ctor, который принимает const char*.

Вот что я, вероятно, сделал бы в этой ситуации.

(Не уверен, почему вы создаете ctor с типом, который вы не передаете для большей части его использования.)

редактировать:

Я вижу, что кто-то уже разместил это, пока я печатал свое

person Tim    schedule 19.03.2009
comment
я надеялся, что RWCString будет обрабатывать как литеральную строку, так и использование RWCString. На самом деле он в основном используется с RWCString; литералы приходят только с некоторыми жестко запрограммированными (не мой выбор) данными конфигурации. - person rmeador; 19.03.2009

Не уверен, почему он должен путать ссылку на строку и логическое значение? Я видел проблемы с bool и int.
Можете ли вы потерять значение по умолчанию для первого конструктора - возможно, поскольку это делает его конструктором по умолчанию для MyClass(), тогда он также является значением по умолчанию, если он может не соответствует аргументам

person Martin Beckett    schedule 19.03.2009
comment
стандарт C++ позволяет преобразовать любой указатель в логическое значение IIRC, поэтому const char * -> bool разрешен в первую очередь. Я просто не знаю, почему он, кажется, нравится больше. Удаление аргумента по умолчанию не идеально и, вероятно, не будет иметь значения, но я попробую. - person rmeador; 19.03.2009

Вы можете добавить конструктор, который явно принимает const char *

MyClass(const char* strPath, bool bLocation = false); // Thanks Andrew Grant!

Or do

MyClass( string("blah") );

Компилятор по своей сути знает, как преобразовать const char * в bool. Ему нужно было бы посмотреть, есть ли для любого из типов конструкторов MyClass с первым аргументом конструктор, который примет исходный тип, который вы ему дали, или может ли он привести его к типу, который принят любой из конструкторов любого из типов с первым аргументом ваших конструкторов MyClass, или... ну, вы видите, к чему это идет, и это только для первого аргумента. На этом пути лежит безумие.

person Rob K    schedule 19.03.2009

Ключевое слово explicit сообщает компилятору, что он не может неявно преобразовать значение типа аргумента в объект вашего класса, как в

struct C { explicit C( int i ): m_i(i) {}; int m_i; };
C c = 10;//disallowed
C c( 2.5 ); // allowed

C++ имеет набор правил для определения того, какой конструктор должен вызываться в вашем случае - я не знаю, что делать, но вы можете интуитивно увидеть, что аргументы по умолчанию ведут к двусмысленности.

Вам нужно продумать эти значения по умолчанию.

Вы можете вернуться к некоторым статическим именованным методам построения. Или вы можете использовать другой класс (что неплохой выбор с точки зрения дизайна). В любом случае вы позволяете клиентскому коду решать, какой конструктор использовать.

struct C {
  static C fromString( const char* s, const bool b = true );
  static C fromBools( const bool abs = true, const bool b = true );
};

or

struct CBase {
    bool absolute; 
    bool location;
    CBase( bool abs=true, bool loc=true );
};

struct CBaseFromPath {
    // makes 'absolute' true if path is absolute
    CBaseFromPath( const char* s, const bool loc );
};
person xtofl    schedule 19.03.2009

Вы уверены, что он действительно вызывает первый конструктор? Вы вызываете его с помощью жестко закодированной строки или она скрыта за #define? Вы уверены, что #define — это то, что вы думаете? (Попробуйте скомпилировать с параметром /Ef, чтобы получить расширенный вывод препроцессора и посмотреть, выглядит ли вызов так, как вы ожидаете.)

РЕДАКТИРОВАТЬ: Основываясь на этом и других комментариях, я предлагаю добавить еще один конструктор для const char*. Возможно ли это?

person Dan Breslau    schedule 19.03.2009
comment
Я вызываю его с помощью простого строкового литерала, без #define, без констант, определенных где-либо еще. И я настолько уверен, насколько это возможно, что он вызывает первый, так как именно туда он переходит, когда я прохожу в отладчике :) - person rmeador; 19.03.2009