Reputation: 147
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.
Upvotes: 6
Views: 6100
Reputation: 41840
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.
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 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.
Upvotes: 10
Reputation: 184
If condition is runtime
To resolve problem with irrelevant return type you can simply modify code in that way:
either<C<A>, C<B>> getObject() {
if(/* something */){
return C<A>{};
}
else{
return C<B>{};
}
}
If condition is compile time
typename std::conditional</* condition */, C<A>, C<B>>::type();
Upvotes: 2
Reputation: 50568
You can simply do this:
template <typename T>
C<T> getObject(){
return {};
}
See it on wandbox.
If you want also to initialize the object, you can do something like this (as long as it has aggregate type as A
or B
):
template <typename T, typename... A>
C<T> getObject(A&&... args){
return { std::forward<A>(args)... };
}
Upvotes: 1
Reputation: 49028
I'm still wondering if there is any useful case where you would want something like that... But the only way I can think off is when condition
can be evaluated at compile time, as that would enable the compiler to use either of the 2 types.
// 'auto' to let the compiler deduce the correct type at compile time.
auto getObject() {
// those 2 branches are evaluated at compile time only, which lets the compiler
// discard the other branch, making the code valid.
if constexpr(/*something at compile time*/)
return C<A>{};
else
return C<B>{};
}
Usage is pretty straightforward:
auto obj = getObject();
Upvotes: 5