estoy
estoy

Reputation: 122

correct approach to conditionally return different types from a template function

I have a function that normally returns integers, but since the semantic of the value may differ i want to strong-type those, so i introduced two types, e.g. Money and Time, simplified as

struct Money {
   uint32_t value;
}

the function will return either Money or Time depending on a bool param. let's say it looks like this:

template <typename T> T getValue(bool mode) {
  Money money;
  Time time;
  ...
  if (mode == ModeMoney) {
   money = something * 2;//get it from somewhere - irrelevant for the example
   return money;
  }
  if (mode == ModeTime) {
   time = something * 100;
   return time;
  }
}

now the compiler will complain for the differing return types, so i add specialized template functions to return the value itself:

template <> Money variableValue<Money>(something) { return something * 2 };
template <> Time variableValue<Time>(something) { return something * 100};

this allows to drop the bool param on invocation and the main function now will change to this:

template <typename T> T getValue(bool mode) {
  ....//calculation of *something* is the same, we only need different output from the function
  return variableValue<T>(something);
}

is this a good approach?

Upvotes: 1

Views: 540

Answers (3)

Jens
Jens

Reputation: 9416

Questions about design issues aside, you seem to want a tagged union or variant variant<Time, Money>. The next C++ standard will have a variant class in the standard library, but until this is available you can use Boost.Variant. With this, the code looks like

std::variant<Money, Time> getValue(bool mode) {
    if (mode) {
        return std::variant<Money, Time>( Money{23} );
    }
    else {
        return std::variant<Money, Time>( Time{42} );        
    }
}

auto m = std::get<Money>( getValue(true) );
auto m2 = std::get<Money>( getValue(false) );
assert( m != nullptr && m2 == nullptr );

You can also implement your own variant for Money and Time only by using a union:

struct TimeOrMoney
{
    enum class Type {isTime, isMoney};
    Type type;
    union
    {
        Time time;
        Money money;
    }
}

An alternative may be to return a std::pair< boost::optional<Money>, boost::optional<Time> > and then check which of the two is actually set. This uses the special optional state as a tag to signal which value has been set.

Upvotes: 0

Sebastian Redl
Sebastian Redl

Reputation: 72215

Following Ami's comment, I recommend you make two functions and a helper:

Time getTime() { return calculateSomething() * 100; }
Money getMoney() { return calculateSomething() * 2; }

Keep separate use cases separate; the primary purpose of templates is to make interfaces simpler, not implementations. But if you turn something that looks like it should be two functions into one using templates, that doesn't make the interface simpler, because it's not intuitive. (Neither, as your question shows, does it actually make the implementation simpler.)

Upvotes: 3

skypjack
skypjack

Reputation: 50568

As an alternative solution, you can use a traits class to do that and to avoid duplicating the code.
It follows a minimal example:

template<typename>
struct traits;

template<>
struct traits<Money> {
    using return_type = Money;
    static constexpr std::size factor = 2;
};

template<>
struct traits<Time> {
    using return_type = Time;
    static constexpr std::size factor = 100;
};

template <typename T>
traits<T>::return_type getValue() {
    traits<T>::return_type ret;
    // ...
    ret = something * traits<T>::factor;
    return ret:
}

If it's a suitable solution mostly depends on the real code and the actual implementation of getValue.

Upvotes: 1

Related Questions