tags:

views:

336

answers:

6

I want do this:
func(conditionA ? pa1 : pa2, conditionB ? pb1 : pb2, conditionC ? pc1 : pc2);

In C style function, there is no problem. But if func() is a template function, compiler will report errors. Here pa1 and pa2, ... are different class and have a static method - "convert()". convert() is also declared as inline for performance consideration.

If template cannot solve this problem, there will be a very looooooooooong if-else like below.

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);
     }
    }
}
+3  A: 

The result of the conditional operator (that is, the a and b in p ? a : b) must be of the same type. That is, you can't do:

predicate() ? 3.14 : "sdfsd"

Make sure your pa1 and pa2 are compatible types (either they are the same type, inherit from one type, or are cast to compatible types). If you indeed have a convert member function that converts those types to compatible types, then why not just use:

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

Finally, it's not sooooo long. You already wrote out the definition. Just keep it generic and move on.

Frank Krueger
+2  A: 

Make pa1 and pa2 inherit from a common base class, and use a reference to that ancestor as the argument type of your (then non-templated) function.

John Zwinck
+1  A: 

The problem you have is with the strong typing at compile time and you wish to change the type at runtime. When the template for Function is compiled it needs to know what what types to provide for. You can use different types, you just can't do it all in 1 line.

Is it really that expensive to do a convert() that you would want to do it only if needed in Function?

Stepping away from template ...

Your description has made the length of the method worse than it might seem, you will need to have your a's b's and c's already defined. You could also reduce the conditions into a switch statement.

If you kept the a, b and c as a complete objects and could ask it for the value to be passed to the function.

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

You could make them have an interface that is common for those methods needed in the function. There are several ways to do this, but easiest is if you can change the class for TypeA1 etc. Adding a parent class of something like IConvertable here.

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

And then implementing convert in each class to call the static version of convert.

Greg Domjan
+1  A: 

It's a bit ugly, but you can turn your 3-level nesting into a single switch statement using bits:

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
}

This split your series of 3 binary decisions into a single 8-way decision, so it's simpler to reason about. Some people may hate it, but I find it quite readable.

Tom
+2  A: 

Your basic problem is that the type of an expression must be knowable at compile time.

Is the condition fixed and known at compile time? If so, it will be possible to use a metafunction to choose each parameter:

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

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

...

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

(Simplified for a 1-parameter case; for 3 parameters you would define e.g. struct choose1, struct choose2, struct choose3.)

Otherwise, your best option is to derive all types from a common base as John Zwinck suggested. The only alternative to that is a gigantic switch statement or list of ifs.

Actually, maybe this is something that Boost.Variant would be good for?

j_random_hacker
A: 

Extending on (repeating) what everyone else has said...

This won't work:

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 );
}

As a and b are different types, and the templated Function is being created for you based on the argument type.

But this WILL work:

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 );
}

Though I really ought to use dynamic_cast...

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