Я хочу создать что-то вроде Variant
на C++. На самом деле я хочу использовать шаблоны как можно меньше. Идея состоит в том, чтобы сохранить значение в union
как с типом переменной, так и вернуть значение в соответствии с сохраненным типом.
Таким образом, тестовый код выглядит следующим образом:
#include <iostream>
#include <vector>
#include <cstring>
#include <typeinfo>
#include <typeindex>
using namespace std;
constexpr uint64_t mix(char m, uint64_t s)
{
return ((s << 7) + ~(s >> 3)) + static_cast<uint64_t>(~m);
}
constexpr uint64_t _(const char* str)
{
return (*str) ? mix(*str,_(str + 1)) : 0;
}
class Variant
{
public:
template<typename T>
Variant(T value):
m_info(typeid(value))
{
std::memcpy(&m_val, &value, sizeof(T));
}
auto toValue() ->decltype(???) // what have I use here ???
{
switch(_(m_info.name()))
{
case _("b"):
return m_val.bval;
case _("i"):
return m_val.ival;
case _("d"):
return m_val.dval;
break;
}
return 0;
}
char cval;
unsigned char ucval;
private:
union Types
{
bool bval;
int ival;
double dval;
} m_val;
std::type_index m_info;
};
Использование:
int main()
{
std::vector<Variant> arr = { 1, 2.2, true };
for(auto &v: arr)
{
cout << "value is: " << v.toValue() << endl;
}
return 0;
}
Но decltype
требует выражения в качестве параметра, и здесь я застрял. Какое выражение я употребил здесь?
К сожалению, C++ так не работает. Типы всех объектов, включая типы возвращаемых функций, должны быть известны во время компиляции, а не во время выполнения. Это фундаментально для C++, и для этого не существует безопасного для типов обходного пути.
typeinfo.name()
не является портативным
Вывод типа для возвращаемых типов не поддерживается в C++. Лучше всего написать отдельные toBool
, toInt
, toDouble
или один template<typename T> T to()
, специализированный для каждого типа, что позволит вам написать v.to<int>()
.
просто напоминание, уже есть std::variant
и std::any
, на случай, если вы захотите сделать это сами.
C++17 использует std::visit
для решения этой проблемы с помощью std::variant
. Это использует шаблон посетителя для решения этой проблемы.
Проблема, которую вы создаете, больше, чем код, который вы предлагаете здесь. Если бы на этот вопрос был дан ответ, и вы смогли получили бы auto x = v.toValue();
для компиляции, что бы вы тогда смогли сделать с x
? Могли ли вы ожидать, что !!x
будет таким же, как x
? Не могли бы вы выразить x % 2
? Вы не могли бы, потому что компилятор больше не знает, что такое тип x
.
Согласно комментарию @UnholySheep, вы пытаетесь создать функцию, тип возвращаемого значения которой выводится во время выполнения, что просто невозможно. Тип возвращаемого значения должен быть известен во время компиляции. Так что вам придется изменить свой API. Здесь есть несколько разных вариантов.
Это похоже на std::variant
, чей API-эквивалент вашего toValue()
выглядит так:
std::get<double>(variant)
std::get<int>(variant)
std::get<bool>(variant)
Этот вызов функции std::get
выдаст std::bad_variant_access
, если вы попытаетесь получить значение неправильного типа. Вы могли бы сделать это здесь.
Другой вариант — извлечь тип union { bool, int, double }
из класса Variant
, чтобы вы могли использовать его в качестве возвращаемого типа. Тогда, вероятно, было бы целесообразно иметь еще один вызов функции, чтобы вызывающая сторона могла сказать во время выполнения, к какому типу относится объединение. Вы можете вернуть перечисление или просто вернуть свою переменную m_type
для этого.
Да, это в основном то же решение, к которому я пришел. Только одна мысль не давала мне покоя. Я знаю сохраненный тип и могу вернуть соответствующее значение...
Однако это не имеет значения, потому что вызывающая сторона не знает сохраненный тип, когда вызывает toValue
. Так что им все равно придется звонить getType
или как-то еще. То же самое и с std::variant
, так как вам нужно вызвать std::holds_alternative<T>
, чтобы проверить тип, прежде чем вы сможете безопасно вызвать std::get<T>
.
Очевидно, вы пытаетесь получить функцию, тип возвращаемого значения которой выводится во время выполнения - это невозможно. Тип возвращаемого значения должен быть известен во время компиляции.