如何根据条件从函数模板返回不同类型?

问题描述 投票:0回答:4

我有以下代码:

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++ c++11 templates polymorphism
4个回答
9
投票

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
和其他形式的类型擦除技术可用。这个答案已经很长了。您可以在网上免费找到大量文档,这些文档深入描述了所有这些内容。


5
投票

我仍然想知道是否有任何有用的情况,你会想要这样的东西......但我能想到的唯一方法是何时可以在编译时评估

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();

2
投票

如果条件是运行时

要解决不相关返回类型的问题,您可以简单地以这种方式修改代码:

either<C<A>, C<B>> getObject() {
  if(/* something */){
    return C<A>{};
  }
  else{
    return C<B>{};
  }
}

任一实施

如果条件是编译时

typename std::conditional</* condition */, C<A>, C<B>>::type();

1
投票

您可以简单地这样做:

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)... };
}
© www.soinside.com 2019 - 2024. All rights reserved.