Mr M.
Mr M.

Reputation: 735

Unknown return type in template

I have a function

template <typename T1, typename T2>
/*return type*/ foo(MyClass<T1>& bar1, MyClass<T2>& bar2)
{
    if (something)
        return bar1;
    else
        return bar2;
}

The problem is, that I don't know what will this function return: it can be either MyClass<T1> or MyClass<T2>.

How can I get it to work?

T1 and T2 are structs of 3 ints, known at compile time. The return type depends on the smaller of those 2: for example for T1 = <5, 1, 1> T2 = <4, 4, 7> return type should be MyClass<T2>.

Example usage:

MyClass<5, 2, 8> m1;
MyClass<4, 2, 1> m2;
cout << foo(m1, m2); //should print m2 (I have a method used for that)

Upvotes: 6

Views: 1479

Answers (6)

Anton Savin
Anton Savin

Reputation: 41301

You can define two functions from which only one will be instantiated for given types:

template <typename T1, typename T2>
struct ChooseFirst;

template <int A1, int B1, int C1, int A2, int B2, int C2>
struct ChooseFirst<MyClass<A1, B1, C1>, MyClass<A2, B2, C2>> {
    // this requires constexpr make_tuple (C++14)
    static const bool value = std::make_tuple(A1, B1, C1) < std::make_tuple(A2, B2, C2);
    // if in your implementation make_tuple is not constexpr, you can write the comparison manually:
    // static const bool value = A1 < A2 || (A1 == A2 && (B1 < B2 || (B1 == B2 && C1 < C2)));
};

template <typename T1, typename T2>
typename std::enable_if<ChooseFirst<T1, T2>::value, T1&>::type foo(T1& bar1, T2&) {
    return bar1;
}

template <typename T1, typename T2>
typename std::enable_if<!ChooseFirst<T1, T2>::value, T2&>::type foo(T1&, T2& bar2) {
    return bar2;
}

Demo

Upvotes: 2

What you are trying to achieve there cannot be done as is. The language does not allow you to have a function that, based on some runtime data, changes its return type. Depending on the problem that you are trying to solve, there might be different alternatives, like determining a common type that can be used to represent either bar1 or bar2 or reorganizing the code so that you don't need to return at all.

That is if something cannot be determined at compile time... if it can be determined you can have some metaprogramming in place to determine the return type to be the one you need.

You should provide a higher level description of the real problem, after which you might get better ideas as of what direction to take.


You can try something in the lines of:

template <bool value> struct Bool {};

template <typename T, typename U>
T f_impl(T t, U u, Bool<true>) { return t; }
template <typename T, typename U>
T f_impl(T t, U u, Bool<false>) { return u; }

template <int A, int B, int C, int D, int E, int F>
auto f(MyClass<A,B,C> a, MyClass<D,E,F> b)
  -> f_impl(a, b, Bool<!(A < D 
                     || (A == D && B < E) 
                     || (A == D && B == E && C < F))>())
{
   return f_impl(a, b, Bool<!(A < D 
                          || (A == D && B < E) 
                          || (A == D && B == E && C < F))>());
}

Upvotes: 4

warsac
warsac

Reputation: 352

Both bar1 and bar2 are references so they are both returned if you change them in some way in your function. You could use a return value to indicate which:

enum ReturnType
{
    BAR1,
    BAR2
}

ReturnType foo(MyClass<T1>& bar1, MyClass<T2>& bar2)
{
    if (something)
        return BAR1;
    else
        return BAR2;
}

int main()
{
    MyClass<T1> t1;
    MyClass<T2> t2;
    ReturnType ret = foo(t1, t2);
    if(ret == BAR1)
    {
        // do what you want in case of MyClass<T1>
    }
    else if(ret == BAR2)
    {
        // do what you want in case of MyClass<T2>
    }
}

An other way that maybe is closer to what you want is to use a base class pointer:

class Base
{
}

template<typename T>
class MyClass : public Base
{
     // ...
}

Base* foo(MyClass<T1>& bar1, MyClass<T2>& bar2)
{
    if (something)
        return &bar1;
    else
        return &bar2;
}

Just as mentioned by vsoftco in the comments and in Walters answer returning a reference works too if that is your preference.

Base& foo(MyClass<T1>& bar1, MyClass<T2>& bar2)
{
    if (something)
        return bar1;
    else
        return bar2;
}

Upvotes: 1

AlexTheo
AlexTheo

Reputation: 4184

I would suggest to make a callback instead of returning value, remember tell don't ask principle:

template<typename T>
struct op{
  void operator ()(T t){}
};

template<>
struct op<int>{
  void operator ()(int a){}
};

template<typename T>
struct Func : public op<T>{
  int a;
};

template<typename T, typename T2>
void foo( Func<T> t, Func<T2> t2){
  t.a = 3;
  t2.a = 4;
  if( t.a > t2.a ){
    t( 3 );
  }else{
    t2( 5 );
  }
}

Maybe there is a better solution. Or you can use the operator () in MyClass, just specialize it.

Upvotes: 2

Walter
Walter

Reputation: 45424

Since the return type must be fixed as compile time, you must return something that can be either MyClass<T1> or MyClass<T2>. This could either be a generic object such as boost::any (a bit of an overkill for this situation) or a common base, a reference (or pointer) to which you then return. This requires your classes to be defined like

class MyClassBase { /* ... */ };
template<typename T>
class MyClass : MyClassBase {  /* ... */ };

template<typename T1, typename T2>
MyClassBase& foo(MyClass<T1>&bar1,MyClass<T2>&bar2)
{
    if (something)
        return bar1;
    else
        return bar2;
}

You can actually use RTTI so that the base MyClassBase can tell what it actually is. In fact, this is roughly how boost::any works.


Of course, as David said, if something is known at compile time, then your code is not really a good design and you should instead use a different one, using compile-time solutions (via template meta-programming techniques).

Upvotes: 1

antimatter
antimatter

Reputation: 3480

You could return a union type, that can either be T1 or T2.

Upvotes: 1

Related Questions