Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Returning different type from a function template depending on a condition

I have the following piece of code:

helper.hpp :

struct A {
   uint32_t a, b;
};

struct B {
  uint32_t a, b;
};

template <typename T>
struct C {
  T barcode;
};

Now based on some condition I want to create appropriate struct object in the main.cpp

if(/* something */) {
  C<A> obj;
}
else {
  C<B> obj;
}

Now the problem is since it's inside the if scope I can't access outside it. One way to handle it would be to return the object from a function, something like this:

template <typename T> 
C<T> getObject(){
  if(/* something */) {
    return C<A>{};
  }
  else{
    return C<B>{};
  }
}

auto obj = getObject()

but this is giving me following compilation error:

error: no matching function for call to 'getObject() note: couldn't deduce template parameter 'T'

Really appreciate any help.

like image 305
Avi Avatar asked Dec 05 '22 14:12

Avi


1 Answers

Types in C++ are determined at compile time. That means that a runtime condition cannot be used to infer the type of an object.

Seeing how you tried to use templates suggest to me that there's a little misunderstanding here. Template code is instanciated by the compiler. Code in templates is not really code until instanciated. If we were to do, by hand, the instanciation of you code using the type A, it would look close to something like this (not actual code):

template <>
auto getObject<A>() -> C<A> {
    if(/* something at runtime */) {
        return C<A>{};
    } else {
        return C<B>{};
    }
}

// auto
C<A> obj = getObject<A>();

As you can see, the code in the else doesn't make sense. You cannot return a value of type C<B> inside a function that must return C<A>. As a side effect of compile time instanciation of code, C<A> and C<B> are unrelated and are completely different types.

Also, you can see that auto has been replaced by C<A>. This is because auto is also inferred at compile time.


Now... What can you do to make your code work?

There are multiple solution and abstraction to have a variable that has a runtime defined type. I'll cover some of the options you can use.

Using a variant

A variant is a class that can hold a single instance of a variable that can be of different types, specified in a finite list of types. For instance, a std::variant<int, std::string> is a variable that can either be a integer or a string.

In your code, it would be a variant of C<A> and C<B>:

auto getObject() -> std::variant<C<A>, C<B>> {
    if (/* something at runtime */) {
        return C<A>{};
    } else {
        return C<B>{};
    }
}

auto obj = getObject();

// The type of obj is std::variant<C<A>, C<B>>

If you don't have access to C++17, you can always use boost::variant.

The downside of this solutions is that you have to know every types the variant can take. If you have a indefinite amount of types, you cannot use variant. It is however, extremely fast and promote regularity ( value semantics ).

virtual polymorphism

Virtual polymorphism is the most common way to get variables of different types decided at runtime. It looks nice but comes with the price of pointer and dynamic allocation, and is rather intrusive. It would look like this:

struct CC {
    virtual ~CC() = default;
};

template<typename T>
struct C : CC {
    T barcode;
};

auto getObject() -> std::unique_ptr<CC> {
    if (/* something at runtime */) {
        return std::make_unique<C<A>>();
    } else {
        return std::make_unique<C<B>>();
    }
}

auto obj = getObject();

// The type of obj is std::unique_ptr<CC>

Note that if this is what you want to do, you have to define some common interface in CC. The rest of the code will use that common interface in order to do operations on C and it's barcode.

Please note that std::make_unique is part of C++14. It can be replaced by std::unique_ptr<C<A>>{new C<A>} and std::unique_ptr<C<B>>{new C<B>} respectively.


There is also std::any and other form of type erasure technique available. This answer is already quite long. There is plenty of documentation you can find online for free that describes all of this in depth.

like image 187
Guillaume Racicot Avatar answered Feb 16 '23 02:02

Guillaume Racicot