我有以下代码:
helper.hpp :
struct A {
uint32_t a, b;
};
struct B {
uint32_t a, b;
};
template <typename T>
struct C {
T barcode;
};
现在根据某些条件我想在 main.cpp 中创建适当的结构对象
if(/* something */) {
C<A> obj;
}
else {
C<B> obj;
}
现在的问题是,因为它位于 if 范围内,所以我无法在其外部访问。 处理它的一种方法是从函数返回对象,如下所示:
template <typename T>
C<T> getObject(){
if(/* something */) {
return C<A>{};
}
else{
return C<B>{};
}
}
auto obj = getObject()
但这给了我以下编译错误:
错误:没有匹配的函数来调用“getObject()” 注意:无法推导出模板参数“T”
非常感谢任何帮助。
C++ 中的类型是在编译时确定的。这意味着运行时条件不能用于推断对象的类型。
看到您如何尝试使用模板,我发现这里存在一些误解。模板代码由编译器实例化。模板中的代码在实例化之前并不是真正的代码。如果我们要使用类型
A
手动实例化代码,它看起来会类似于这样 (不是实际的代码):
template <>
auto getObject<A>() -> C<A> {
if(/* something at runtime */) {
return C<A>{};
} else {
return C<B>{};
}
}
// auto
C<A> obj = getObject<A>();
如你所见,else中的代码没有意义。您不能在必须返回
C<B>
的函数内返回 C<A>
类型的值。作为代码编译时实例化的副作用,C<A>
和 C<B>
不相关,并且是完全不同的类型。
此外,您还可以看到
auto
已替换为 C<A>
。这是因为 auto
也在编译时推断。
现在...你可以做什么来让你的代码正常工作?
有多种解决方案和抽象来使变量具有运行时定义的类型。我将介绍一些您可以使用的选项。
变体是一个类,它可以保存变量的单个实例,该变量可以是不同类型的,在有限类型列表中指定。例如,
std::variant<int, std::string>
是一个变量,可以是整数也可以是字符串。
在您的代码中,它将是
C<A>
和 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>>
如果您无法访问 C++17,您可以随时使用
boost::variant
。
此解决方案的缺点是您必须了解变体可以采用的每种类型。如果类型数量不定,则不能使用变体。然而,它非常快并且促进规律性(值语义)。
虚拟多态性是在运行时决定不同类型变量的最常见方法。它看起来不错,但代价是指针和动态分配,并且相当具有侵入性。它看起来像这样:
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>
请注意,如果这是您想要做的,您必须在
CC
中定义一些通用接口。代码的其余部分将使用该通用接口来对 C
及其条形码进行操作。
请注意,
std::make_unique
是 C++14 的一部分。可以分别替换为 std::unique_ptr<C<A>>{new C<A>}
和 std::unique_ptr<C<B>>{new C<B>}
。
还有
std::any
和其他形式的类型擦除技术可用。这个答案已经很长了。您可以在网上免费找到大量文档,这些文档深入描述了所有这些内容。
我仍然想知道是否有任何有用的情况,你会想要这样的东西......但我能想到的唯一方法是何时可以在编译时评估
condition
,因为这将使编译器能够使用两种类型中的任何一种。
// '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>{};
}
用法非常简单:
auto obj = getObject();
如果条件是运行时
要解决不相关返回类型的问题,您可以简单地以这种方式修改代码:
either<C<A>, C<B>> getObject() {
if(/* something */){
return C<A>{};
}
else{
return C<B>{};
}
}
如果条件是编译时
typename std::conditional</* condition */, C<A>, C<B>>::type();
您可以简单地这样做:
template <typename T>
C<T> getObject(){
return {};
}
在 wandbox 上查看。
如果您还想初始化该对象,您可以执行类似的操作(只要它的聚合类型为
A
或 B
):
template <typename T, typename... A>
C<T> getObject(A&&... args){
return { std::forward<A>(args)... };
}