Избежание неоднозначности в разрешении перегрузки

Это продолжение этого вопроса, поэтому, если вам нужно увидеть класс 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);

Я не могу рекомендовать это, хотя, это больше проблем, чем стоит. Я предлагаю использовать решение, которое я назвал "правильный путь". Это наименее сложный и тот, который труднее всего использовать неправильно позже

Другие вопросы по тегам