如何在C++中使用枚举类型专门指定函数的返回类型?

4

我在C++中使用变体来存储语法解析器的一系列类型。每个语法规则的组成部分都有一个类别(类型为枚举)和一个值。该组成部分根据其类别存储一种类型的值。为了举例说明,我将类别简化为'String' => 存储字符串,'Number' => 存储整数。

我想要根据其类别枚举获取组成部分的正确类型的值。我该怎么做?

我编写了下面的示例代码,其中构造了两个组成部分:strCon,存储一个字符串;intCon,存储一个整数,并尝试获取它们的值。

我想将strCon中的字符串赋值给strVal,将intCon中的整数赋值给intVal。

#include <variant>

struct Constituent
{
    enum class Category {String, Number};
    using Value = std::variant<std::string, int>;

    Category cat;
    Value val;

    // Using a struct ideally to allow partial specialisation of the template,
    // so I can pass the enum without the return type.
    template<Category T>
    struct OfCategory {};

    template<Category T, typename U>
    friend U const& getValue(OfCategory<T>, Constituent const&);
}

using Category = Constituent::Category;

// Template to return the value as the correct type
// for the constituent's category.
template<Category T, typename U>
U const& getValue(OfCategory<T> type, Constituent const& constituent)
{
    // Uses the variant's get function.
    return std::get<U>(constituent.val);
}

// Specialisation to return string from Category::String.
template<>
string const& getValue(OfCategory<Category::String> type,
    Constituent const& constituent)
{
    return getValue<Category::String, string>(constituent);
}

// Specialisation to return int from Category::Number.
template<>
int const& getValue(OfCategory<Category::Number> type,
    Constituent const& constituent)
{
    return getValue<Category::Number, int>(constituent);
}

int main()
{
    Constituent strCon = {Category::String, "This is a string!"};
    Constituent intCon = {Category::Number, 20};

    // In my current implementation, I want this to work with
    // the type wrapper as an overload for the function.
    string strVal = getValue(OfCategory<Category::String>{}, strCon);
    int intVal = getValue(OfCategory<Category::Number>{}, intCon);

    // But it would be better to directly use the template.
    strVal = getValue<Category::String>(strCon);
    intVal = getValue<Category::Number>(intCon);

    // The only way I can get it to work, is to explicitly provide
    // the return type, which defeats the point.
    strVal = getValue<Category::String, string>(
        OfCategory<Category::String>{}, strCon);
    intVal = getValue<Category::Number, int>(
        OfCategory<Category::Number>{}, intCon);

    // Ideally, I could use the cat parameter in Constituent to dynamically
    // infer the return type, but I don't believe something like this is
    // possible in C++.
}

你实际上不需要存储 catval.index() 提供了等效的信息。而且你的 getValues 似乎在重新发明 std::get - Igor Tandetnik
@IgorTandetnik,我认为我做不到,因为我有一些共享相同值类型的类别。 - kiechant
1
std::variant 可以有重复的类型;std::variant<int, int> 是有效的。std::get 可以接受一个整数索引。 - Igor Tandetnik
3个回答

3

你可以通过创建一个中间的traits类来实现一级间接性:

enum E
{
    X,
    Y
};

template <E e>
struct Traits;

template <>
struct Traits<X>
{
    using type = std::string;
};

template <>
struct Traits<Y>
{
    using type = int;
};

template <E e>
typename Traits<e>::type get();

template <>
typename Traits<X>::type get<X>()
{
    return "";
}

template <>
// actually, using the appropriate type directly works as well...
int get<Y>()
{
    return 7;
}

现在您可以像这样调用函数:
std::string s = get<X>();
int n = get<Y>();

谢谢回答!你和@Jarod42都回答得很好,但我选择了Jarod的作为官方答案,因为它直接涉及到了示例。 - kiechant

1

您需要添加一些特性来从枚举中提供类型,例如重用OfCategory

template<Category T> struct OfCategory;

template<> struct OfCategory<Category::String> { using type = std::string; };
template<> struct OfCategory<Category::Number> { using type = int; };

然后,无需额外的专业知识:
template <Category T>
const typename OfCategory<T>::type&
getValue(OfCategory<T> type, Constituent const& constituent)
{
    // Uses the variant's get function.
    return std::get<typename OfCategory<T>::type>(constituent.val);
}

针对这样的调用:getValue(OfCategory<Category::String>{}, strCon)

甚至还有:

template <Category T>
const typename OfCategory<T>::type&
getValue(Constituent const& constituent)
{
    // Uses the variant's get function.
    return std::get<typename OfCategory<T>::type>(constituent.val);
}

对于像 getValue<Category::String>(strCon); 这样的调用


这正是我一直在寻找的。现在看来很明显,要从枚举中获取类型,我只需要从枚举中创建一个类型即可! - kiechant

0
我猜应该是这样的:
template<Category T>
auto getValue(OfCategory<T> type, Constituent const& constituent)
    -> decltype(std::get<T>(constituent.val))
{
    return std::get<T>(constituent.val);
}

(可能需要将T强制转换为size_t)。换句话说,您的getValuestd::get的重新实现。


谢谢!我认为这是解决我的问题最简单的方法。我没有想到变量中可以有重复的类型,并将枚举与类型的索引相关联。不过,我还是把答案给了@Jarod42,因为我认为它最好地回答了标题中的问题。 - kiechant
@Aconcagua 哦,是的,那就是我想用的。已修复。 - Igor Tandetnik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接