Как специализировать возвращаемый тип функции с перечислением в С++?

Я использую вариант для хранения диапазона типов для синтаксического анализатора на C++. Каждая составляющая синтаксического правила имеет категорию (типа enum) и значение. Компонент хранит тип значения в соответствии с категорией. Для примера я упростил категории до «Строка» => хранит строку, а «Число» => хранит целое число.

Я хотел бы получить значение компонента с правильным типом в соответствии с его перечислением категории. Как я могу это сделать?

Ниже я написал пример кода, в котором я создаю два компонента: strCon, сохраняющий строку, и intCon, сохраняющий целое число, и пытаюсь получить их значения.

Я хочу присвоить строку из strCon в strVal, а int из 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++.
}

person kiechant    schedule 25.06.2019    source источник
comment
Вам не нужно хранить cat. val.index() предоставляет эквивалентную информацию. И ваш getValues, кажется, заново изобретает std::get   -  person Igor Tandetnik    schedule 25.06.2019
comment
@IgorTandetnik Я не думаю, что смогу это сделать, потому что у меня есть категории с одинаковым типом значения.   -  person kiechant    schedule 25.06.2019
comment
std::variant может иметь повторяющиеся типы; std::variant<int, int> действителен. std::get может принимать целочисленный индекс.   -  person Igor Tandetnik    schedule 25.06.2019


Ответы (3)


Вам нужно добавить некоторые трейты для предоставления типа из перечисления, например повторное использование 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);

person Jarod42    schedule 25.06.2019
comment
Это именно то, что я искал. Теперь это кажется настолько очевидным, что для получения типа из перечисления мне просто нужно сделать тип из перечисления! - person kiechant; 26.06.2019

Вы можете использовать один уровень косвенности, создав класс промежуточных признаков:

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>();
person Aconcagua    schedule 25.06.2019
comment
Спасибо за ответ! И вы, и @Jarod42 хорошо ответили на вопрос, но я сделал официальный ответ Джарода, потому что в нем есть прямая ссылка на пример. - person kiechant; 26.06.2019

Я подозреваю, что что-то вроде этого будет работать:

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). Другими словами, ваш getValue — это новое изобретение std::get.

person Igor Tandetnik    schedule 25.06.2019
comment
Спасибо за это! Я думаю, что это самый простой способ решить мою проблему. Мне не приходило в голову, что я могу иметь повторяющиеся типы в варианте и связать перечисление с индексом типа. Я дал ответ @Jarod42, потому что я думаю, что он лучше всего отвечает на вопрос в заголовке. - person kiechant; 26.06.2019
comment
@Aconcagua А, да, это то, что я хотел использовать. Фиксированный. - person Igor Tandetnik; 26.06.2019