Подтвердить что ты не робот

Почему (false? A(): B()). Test() компилируется только тогда, когда A и B имеют отношение подкласса?

Изначально мне нравится использовать что-то вроде этого:

(true?a:b).test()

вместо

(true?a.test():b.test())

чтобы сохранить время ввода, если функция имеет то же имя, изначально я думал, что она должна быть действительной, но я обнаружил:

#include <stdio.h>
class A{
public:
    char test(){
        return 'A';
    }
};

class B{
public:
    char test(){
        return 'B';
    }
};

int main(){
    printf("%c\n",(false?A():B()).test());
    return 0;
}

не может компилироваться, но если B является подклассом A:

#include <stdio.h>
class A{
public:
    char test(){
        return 'A';
    }
};

class B : public A{
public:
    char test(){
        return 'B';
    }
};

int main(){
    printf("%c\n",(false?A():B()).test());
    return 0;
}

он может скомпилировать, почему?

4b9b3361

Ответ 1

Причина в том, что (test?a:b) является выражением и должен иметь тип. Этот тип является общим типом a и b, а несвязанные типы не имеют общего типа. Общий тип базового и производного классов - это базовый класс.

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

Ответ 2

Операторы условного оператора (?:) должны иметь общий тип. То есть учитывая E1 ? E2 : E3, то E2 и E3 должны быть однозначно конвертируемыми. Этот тип - это тип возврата, который затем используется для выражения в целом.

Из cppreference они перечисляют правила и требования, но важная строка, которая здесь имеет значение:

Обратный тип условного оператора также доступен как двоичный тип std::common_type

Что в основном говорит о том, что должен быть общий тип, а std::common_type может использоваться для вычисления этого типа.

На основе вашего фрагмента кода (true ? a.test() : b.test()) работал, поскольку оба a.test() и b.test() возвращены char. Однако a и b не связаны друг с другом, поэтому не могут использоваться сами по себе.


Соответствующий материал в стандарте С++ (WD n4527) найден §5.16 ([expr.cond]). Существует несколько правил и преобразований, суть которых заключается в том, что , если нет преобразования, или если преобразование неоднозначно, программа плохо сформирована.

Ответ 3

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

Это может показаться неожиданным, что может показаться неожиданным, одним из таких интересных случаев будет безъядерная лямбда с совместимой сигнатурой функции после преобразования в указатель функции, например:

#include <iostream>
#include <algorithm>
#include <vector>

int main() {
    std::vector<int> v(0, 10);
    bool increase = true;
    std::sort(v.begin(), v.end(), increase ? 
          [](int lhs, int rhs){return lhs < rhs;} : 
          [](int lhs, int rhs){return lhs > rhs;} );
    return 0;
} 

. Мы можем видеть из следующего отчета об ошибке. Это также относится в целом и, в частности, для этого вопроса относится к классам без общей базы. Следующий пример взят из отчета об ошибке:

struct A{ typedef void (*F)(); operator F(); };
struct B{ typedef void (*F)(); operator F(); };

void f() {
  false ? A() : B();
}

действителен, так как безъядерный лямбда-регистр как A, так и B может быть преобразован в указатель функции.

Для справки проект стандарта С++ в разделе 5.16 [expr.cond] говорит:

В противном случае, если второй и третий операнды имеют разные типы и имеют (возможно, cv-квалифицированный) класс type, или если оба являются значениями одной и той же категории значений и того же типа, за исключением cv-квалификации, делается попытка преобразовать каждый из этих операндов в тип другого.

а затем охватывает правила, а затем говорит:

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

Ответ 4

Добавьте его на язык:

template<class F>
struct if_t {
  bool b;
  F f;
  template<class Lhs, class Rhs>
  auto operator()(Lhs&&lhs, Rhs&&rhs)&&
  ->std::result_of_t<F(Lhs)>
  {
    if (b) return std::forward<F>(f)(std::forward<Lhs>(lhs));
    else return std::forward<F>(f)(std::forward<Rhs>(rhs));
  }
  template<class Lhs>
  void operator()(Lhs&&lhs)&&
  {
    if (b)
      std::forward<F>(f)(std::forward<Lhs>(lhs));
  }
};

template<class F>
if_t<std::decay_t<F>> branch(bool b, F&& f){
  return {b,std::forward<F>(f)};
}

то получим:

branch(false, [&](auto&&arg){return arg.test();})
(
  A{}, B{}
);

где он работает только в том случае, если оба A и B имеют .test(), а возвращаемое значение B::test() можно преобразовать в возвращаемое значение A::test().

К сожалению, построены как A{}, так и B{}.

template<class T>
auto make(){return [](auto&&...args){return {decltype(args)(args)...};}}

branch(false, [&](auto&&arg){return arg().test();})
(
  make<A>(), make<B>()
);

который отличается конструкцией.

Не самый элегантный синтаксис. Его можно очистить, но недостаточно (по-моему). Невозможно создать ленивых операторов на С++ с чистым синтаксисом, вы можете использовать только встроенные.

Во всяком случае, ваш код не может работать, потому что ?: - это выражение, которое возвращает тип. Нет типа, который может представлять как A, так и B, поэтому он не может работать. Если бы кто-то был базой другого, или было преобразование и т.д., Тогда это сработало бы.