Избежание неоднозначности в разрешении перегрузки
Это продолжение этого вопроса, поэтому, если вам нужно увидеть класс Register, обратитесь к этому вопросу. Теперь, основываясь на предоставленном ответе, я написал функцию для этого. У меня есть 2 версии функции: одна будет сохранять результаты обратно в оригинал, а вторая - копию. Вот мои функции:
template<std::uint64_t N>
void reverseBitOrder( Register<N>& reg ) {
auto str = reg.register_.to_string();
std::reverse(str.begin(), str.end());
auto x = vpc::Byte(str);
reg.register_ = x;
}
// Extra unused parameter to avoid ambiguity
template<std::uint64_t N>
Register<N> reverseBitOrder(Register<N>& reg, bool _x_ /*not used*/ ) {
auto str = reg.register_.to_string();
std::reverse(str.begin(), str.end());
auto x = vpc::Byte(str);
Register<N> temp;
temp.register_ = x;
return temp;
}
Первый сохраняет значение, второй возвращает копию. У меня вопрос по 2-й функции. Я закончил тем, что добавил второй параметр, который не используется, чтобы избежать неоднозначности из-за разрешения перегрузки, поскольку функции не могут быть разрешены только для возвращаемых типов. Поэтому, когда я вызываю эту функцию, мне нужно будет передать либо 0
, 1
, true
или же false
к функции, которая не имеет никакого эффекта.
В целом, это само по себе не очень большое дело, однако, оно не кажется мне очень чистым и лаконичным. Есть ли другие способы добиться этого? Я предпочитаю не делать это функцией класса. Мой класс или структура регистра завершены как есть, и любые операции над регистром будут выполняться функциями, которые ссылаются на один или несколько объектов регистра.
2 ответа
Ты можешь использовать std::optional
для достижения этой цели.
return
тип шаблона функции reverseBitOrder
должно быть std::optional<vpc::Register<N>>
,
Шаблон функции должен быть изменен на:
template<std::uint64_t N>
std::optional<vpc::Register<N>> reverseBitOrder(vpc::Register<N>& reg, bool _x_ = false) {
auto str = reg.register_.to_string();
std::reverse(str.begin(), str.end());
vpc::Register<N> temp;
if(_x_) //return copy
{
temp.register_ = vpc::Byte(str); //since register_ is a vpc::Byte. Generalize accordingly.
return temp;
}
else //save in the same variable
{
reg.register_ = vpc::Byte(str);
return {};
}
}
Live Demo here
,
Но вам не нужно использовать std::optional
, поскольку в шаблоне функции действительно нет случая "сбоя".
Если я правильно понимаю, обе функции вычисляют x
и хранить его в Register<N>
, но один возвращает указанный объект по значению, в то время как другой сохраняет результат в аргументе функции reg
,
Технически это может быть сделано путем перегрузки константности путем определения этих двух функций:
template<std::uint64_t N> void reverseBitOrder( Register<N>& reg );
template<std::uint64_t N> Register<N> reverseBitOrder( Register<N> const& reg );
Хотя это технически отвечает на ваш вопрос, это было бы ужасно. Если я не ошибаюсь, реальная проблема заключается в следующем:
// Behaviour 1: value stored in reg
reverseBitOrder(reg);
// Behaviour 2: value stored in val, reg left untouched
auto val = reverseBitOrder(reg);
Проблема в том, что используется возвращаемое значение или нет - это не то, что вы можете обнаружить изнутри функции.
Правильный способ сделать так, чтобы одна функция делала две вещи, состоял бы в том, чтобы иметь функцию с этой сигнатурой:
template<std::uint64_t N> void reverseBitOrder( Register<N> const& inputReg, Register<N>& outputReg );
Эта функция будет использовать inputReg
вычислить x
затем сохраните результат в outputReg
Это означает, что вы будете использовать это так:
// Behaviour 1: value stored in reg
reverseBitOrder(reg, reg);
// Behaviour 2: value stored in val, reg leftuntouched
reverseBitOrder(reg, val);
Теперь, если это действительно не сработает для вас, есть способ получить нужный синтаксический сахар за счет ненужной сложности и добавления конструктора к Register<N>
, Это будет выглядеть примерно так:
// Forward declaration
template<std::uint64_t N>
class Register;
// Proxy class
template<std::uint64_t N>
struct RegisterProxy
{
Register<N>* reg;
TypeOfX x;
~RegisterProxy()
{
if (reg)
{
reg->register = x;
}
}
};
// Modified Register class
template<std::uint64_t N>
class Register
{
...
Register(RegisterProxy& proxy)
{
...
register = proxy.x;
proxy.reg = nullptr;
...
}
...
// Define the matching assignment operator too
...
};
// Your function
template<std::uint64_t N>
RegisterProxy<N> reverseBitOrder( Register<N>& reg )
{
auto str = reg.register_.to_string();
std::reverse(str.begin(), str.end());
auto x = vpc::Byte(str);
return RegisterProxy{reg, x};
}
Это позволяет вам сделать
// Behaviour 1: temporary object immediately destroyed and value stored in reg
reverseBitOrder(reg);
// Behaviour 2: constructor called, value stored in val
// the temporary object's destructor doesn't do anything, reg left untouched
auto val = reverseBitOrder(reg);
Я не могу рекомендовать это, хотя, это больше проблем, чем стоит. Я предлагаю использовать решение, которое я назвал "правильный путь". Это наименее сложный и тот, который труднее всего использовать неправильно позже