Может ли шаблон С++ сделать это для условий улучшения кода?

Я хочу сделать следующее:
func(conditionA ? pa1 : pa2, conditionB ? pb1 : pb2, conditionC ? pc1 : pc2);

В функции стиля C нет проблем. Но если func() является шаблонной функцией, компилятор сообщит об ошибках. Здесь pa1 и pa2,... относятся к разным классам и имеют метод static - "convert()". convert() также объявляется как inline из соображений производительности.

Если шаблон не может решить эту проблему, будет очень долгооооооооооооооооооооооолгото если-иначе, как показано ниже.

if (conditionA)
{
    typeA1    a;
    if (conditionB)
    {
        typeB1    b;
        if (conditonC)
        {
            C1    c;
            Function(a, b, c);
        }
        else
        {
            C2    c;
            Function(a, b, c);
        }
    }
    else
    {
        typeB2    b;
        if (conditonC)
        {
            C1    c;
            Function(a, b, c);
        }
        else
        {
            C2    c;
            Function(a, b, c);
        }
    }
}
else
{
    typeA2    a;
    if (conditionB)
    {
        typeB1    b;
        if (conditonC)
        {
            C1    c;
            Function(a, b, c);
        }
        else
        {
            C2    c;
            Function(a, b, c);
        }
    }
    else
    {
        typeB2    b;
        if (conditonC)
        {
            C1    c;
            Function(a, b, c);
        }
        else
        {
            C2    c;
            Function(a, b, c);
        }
    }
}

person Eric    schedule 09.01.2009    source источник
comment
являются ли условия постоянной времени компиляции?   -  person Evan Teran    schedule 09.01.2009
comment
Вы упоминаете convert() в своих комментариях, но нигде в коде этого нет. Вы можете уточнить?   -  person John Zwinck    schedule 09.01.2009


Ответы (6)


Результат условного оператора (то есть a и b в p ? a : b) должен быть одного типа. То есть нельзя:

predicate() ? 3.14 : "sdfsd"

Убедитесь, что ваши pa1 и pa2 являются совместимыми типами (либо они одного типа, либо наследуются от одного типа, либо приведены к совместимым типам). Если у вас действительно есть функция-член convert, которая преобразует эти типы в совместимые типы, то почему бы просто не использовать:

conditionA ? pa1.convert() : pa2.convert()

Наконец, это не оооочень долго. Вы уже написали определение. Просто оставьте его общим и двигайтесь дальше.

person Frank Krueger    schedule 09.01.2009

Сделайте так, чтобы pa1 и pa2 наследовались от общего базового класса, и используйте ссылку на этого предка в качестве типа аргумента вашей (тогда еще не шаблонной) функции.

person John Zwinck    schedule 09.01.2009

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

Является ли условие фиксированным и известным во время компиляции? Если это так, можно будет использовать метафункцию для выбора каждого параметра:

template <bool B>
struct choose {
    typedef X type;
};

template <>
struct choose<false> {
    typedef Y type;
};

...

func(choose<a_constant_bool_expr>::type());

(Упрощено для случая с 1 параметром; для 3 параметров вы должны определить, например, struct choose1, struct choose2, struct choose3.)

В противном случае вам лучше всего вывести все типы из общей базы, как предложил Джон Цвинк. Единственная альтернатива этому — гигантский оператор switch или список if.

На самом деле, может быть, для этого подойдет Boost.Variant?

person j_random_hacker    schedule 09.01.2009

У вас есть проблема со строгой типизацией во время компиляции, и вы хотите изменить тип во время выполнения. Когда шаблон для функции скомпилирован, ему необходимо знать, какие типы следует предоставлять. Вы можете использовать разные типы, вы просто не можете сделать все это в 1 строке.

Неужели так дорого делать convert(), что вы хотели бы делать это только в случае необходимости в функции?

Отходя от шаблона...

Ваше описание сделало длину метода хуже, чем может показаться, вам нужно будет уже определить ваши a, b и c. Вы также можете сократить условия до оператора switch.

Если бы вы сохранили a, b и c как полные объекты и могли бы запросить значение, которое будет передано в функцию.

class a {
  p1, p2;
  condition;
  val value() { return condition? p1.convert, p2.convert };
}

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

class IConvertable{
public: 
  ~IConvertable(){}
  virtual val convert() = 0;
}

А затем реализовать convert в каждом классе для вызова статической версии convert.

person Greg Domjan    schedule 09.01.2009

Это немного некрасиво, но вы можете превратить свою трехуровневую вложенность в один оператор switch, используя биты:

const unsigned int caseA = 1 << 0;
const unsigned int caseB = 1 << 1;
const unsigned int caseC = 1 << 2;
switch ((conditionA ? caseA : 0) | (conditionB ? caseB : 0) | (conditionC ? caseC : 0)) {
    case 0:                            func(pa2, pb2, pc2); break;
    case caseA:                     func(pa1, pb2, pc2); break;
    case caseB:                     func(pa2, pb1, pc2); break;
    case caseA|caseB:           func(pa1, pb1, pc2); break;
    case caseC:                     func(pa2, pb2, pa1); break;
    case caseA|caseC:           func(pa1, pb2, pc1); break;
    case caseB|caseC:           func(pa2, pb1, pc1); break;
    case caseA|caseB|caseC: func(pa1, pb1, pc1); break;
    default: assert(false); // unreachable
}

Это разделит вашу серию из 3 бинарных решений на одно 8-стороннее решение, так что об этом будет проще рассуждать. Некоторым это может не понравиться, но я нахожу это вполне читаемым.

person Tom    schedule 09.01.2009

Продолжая (повторяя) то, что сказали все остальные...

Это не будет работать:

template<class TYPE>
inline void Function( TYPE & object )
{
  cout << "Function():  " << object.convert() << endl;
}

class A
{
public:
  static const char * convert() { return "classA"; }
};

class B
{
public:
  static const char * convert() { return "classB"; }
};

int
main(int argc)
{
  A a;
  B b;

  Function( argc>1 ? a : b );
}

Поскольку a и b — это разные типы, шаблонная Функция создается для вас на основе типа аргумента.

Но это будет работать:

template<class TYPE>
inline void Function( TYPE & object )
{
  cout << "Function():  " << object.convert() << endl;
}

class C
{
public:
  virtual const char * convert() = 0;
};

class A : public C
{
public:
  static const char * staticConvert() { return "classA"; }
  const char * convert() { return A::staticConvert(); }
};

class B : public C
{
public:
  static const char * staticConvert() { return "classB"; }
  const char * convert() { return B::staticConvert(); }
};

int
main(int argc)
{
  A a;
  B b;

  Function( argc>1 ? (C&)a : (C&)b );
}

Хотя я действительно должен использовать dynamic_cast...

  Function( argc>1 ? dynamic_cast<C&>(a) : dynamic_cast<C&>(b) )
person Mr.Ree    schedule 09.01.2009