如何在带有模板函数的 class 中使用类型安全的联合(变体)?

How can I use type-safe unions (variants) inside a class with template functions?

我想用模板函数将 std::variant 放在 class 和 return 的元素中。这是一个例子:

#include <string>
#include <variant>
class Class {
   public:
    std::variant<std::string, double> cont;
    Class() {}
    template <class V> Class(const V v) { cont = v; }
    template <typename V> V fun() {
        if (std::holds_alternative<double>(cont))
            return std::get<double>(cont);
        else if (std::holds_alternative<std::string>(cont))
            return std::get<std::string>(cont);
    }
};

int main() {
    Class c;
    c = 20;
    double d = c.fun<double>();
    return 0;
}

我尝试通过模板函数funreturnclassClass的元素。但是,gcc-9.1 拒绝编译代码并告诉我

Class.cpp:12:46: error: cannot convert ‘std::__cxx11::basic_string<char>’ to ‘double’ in return
   12 |             return std::get<std::string>(cont);

为什么有人试图将 string(函数 foo 的第二个 return 类型)转换为 double?我可以防止这种情况并解决问题吗?我是否使用 std::variant class 单字?

这里的问题是你在运行时查询存储的当前值,而模板实例化的函数签名在编译时执行[=22] =].考虑当您尝试使用成员函数检索 double:

时成员函数的样子
double fun() {
    if (/* ... */)
        return std::get<double>(cont); // Ok return type is double, too
    else if (/* ... */)
        return std::get<std::string>(cont); // Error, return type should be string?!
}

这行不通。您需要更改访问数据成员的方式,例如通过提供两个 getter-like 函数返回 std::optional<double>std::optional<std::string> 或类似的东西,将重载集传递给 std::visit

所有运行时 if 分支都必须是可编译的,即使没有采用。如果我们用 V == double 调用 fun() 那么 returning 一个 std::string 是没有意义的并且会导致错误(即使永远不会采用该分支,编译器也不知道那是肯定的)。

相反,只需 return 它立即通过 V:

template <typename V> V fun() {
    if (std::holds_alternative<V>(cont))
        return std::get<V>(cont);
    return {}; // return default constructed V. You could throw an exception here instead etc.
}