Почему тернарный оператор здесь не то же самое, что if-else?

Я использую std::function TR1 для реализации простого механизма обратного вызова. Если я не хочу, чтобы мне перезвонили, я регистрирую nullptr в качестве обработчика обратного вызова. Это компилируется и работает нормально:

void Foo::MessageHandlingEnabled( bool enable ){
    if( enable )
        m_Bar.RegisterMessageHandler( std::bind(&Foo::BarMessageHandler, this, std::placeholders::_1) );
    else
        m_Bar.RegisterMessageHandler( nullptr );
}

Если я перепишу это с помощью тернарного оператора...

void Foo::MessageHandlingEnabled( bool enable ){
        m_Bar.RegisterMessageHandler( enable?
                                      std::bind(&Foo::BarMessageHandler, this, std::placeholders::_1) :
                                      nullptr );   

 }

... Компилятор VC++ говорит:

ошибка C2446: ':': нет преобразования из 'nullptr' в 'std::tr1::_Bind‹_Result_type,_Ret,_BindN>' 1> с 1>
[ 1> _Result_type=void, 1> _Ret=void , 1>
_BindN=std::tr1::_Bind2,Foo *,std::tr1::_Ph‹1>> 1> ] 1> Ни один конструктор не мог принять исходный тип, или разрешение перегрузки конструктора было неоднозначным

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


person dario_ramos    schedule 11.10.2012    source источник


Ответы (3)


Обе ветви тернарного оператора должны возвращать значения одного типа или тип одного значения должен быть конвертируемым в другой.

5.16.3 ... если второй и третий операнд имеют разные типы и любой из них имеет тип класса (возможно, cv-qualified), предпринимается попытка преобразовать каждый из этих операндов в тип другого... [подробности опущены ] С помощью этого процесса определяется, можно ли преобразовать второй операнд в соответствие с третьим операндом и можно ли преобразовать третий операнд в соответствие со вторым операндом. Если оба могут быть преобразованы, или одно может быть преобразовано, но преобразование неоднозначно, программа плохо сформирована. Если возможно только одно преобразование, это преобразование применяется к выбранному операнду, и преобразованный операнд используется вместо исходного операнда до конца этого раздела.

Вот почему ошибка компилятора говорит ...no conversion from 'nullptr' to 'std::tr1::_Bind<_Result_type,_Ret,_BindN>' 1>...

nullptr имеет тип std::nullptr_t, а std::function<> имеет конструктор, который принимает std::nullptr_t. std::tr1::_Bind нельзя преобразовать в std::nullptr_t или наоборот в контексте тернарного оператора.

if/else, с другой стороны, вообще ничего не возвращает.

person Maxim Egorushkin    schedule 11.10.2012
comment
Поскольку if не входит в список аргументов метода (и не может синтаксически), любое упоминание «возвращаемого значения» для блока if здесь вводит в заблуждение. - person Konrad Rudolph; 11.10.2012
comment
Что меня озадачивает, так это то, что если вы можете передать nullptr при вызове RegisterMessageHandler, который ожидает std::function<void(std::string), почему его нельзя преобразовать при использовании тернарного оператора? - person dario_ramos; 11.10.2012

Здесь я полагаю, что registerHandler имеет полиморфное объявление.

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

Таким образом, разрешая полиморфизм registerHandler, чтобы он соответствовал тому, который принимает аргумент, совместимый с результатом привязки.

При использовании if каждый вызов registerHandler разрешается отдельно, поэтому правильный выбор registerHandler зависит от каждого переданного типа параметра.

person dweeves    schedule 11.10.2012
comment
Да, это виртуальный метод. Но, судя по другому ответу, то же самое произойдет и с невиртуальным методом. Я должен был бы проверить это, но я почти уверен, что функция регистрации, являющаяся виртуальной или нет, здесь не имеет значения. - person dario_ramos; 11.10.2012
comment
Здесь дело не в виртуальности, а в полиморфизме. то есть: одно и то же имя метода, разные типы параметров. - person dweeves; 11.10.2012
comment
Обычно я называю это перегрузкой, но это нормально, некоторые люди считают это формой полиморфизма. Перегрузки в моем случае тоже нет. - person dario_ramos; 11.10.2012
comment
мое предположение таково: класс объявляет registerHandler (someType a) и registerHandler (someOtherType b) с не конвертируемым в b. при использовании тройки с SomeType слева от : и someOtherType справа от : единственным разрешенным символом является registerHandler(someType) для вызова - person dweeves; 11.10.2012

Ответ Максима Егорушкина правильный. Вот простой обходной путь с примером кода, который лучше иллюстрирует вашу проблему:

struct Base{};
struct DerivedA:public Base{};
struct DerivedB:public Base{};

DerivedA a;
DerivedB b;

doesNotWork()
{
   bool chooseA = true;
   Base& base = chooseA?a:b; // Error: compiler tries to convert b to DerivedA (the type of a).
}

Base& choose(bool x)
{
   if(x) return a;
   return b;
}

works()
{
   bool chooseA = true;
   Base& base = choose(chooseA); //Helper function converts a or b to parent class Base.
}
person kirsch    schedule 16.04.2014