Chris Ogle Chris Ogle - 5 days ago 5
C++ Question

Universal Reference: Cannot convert parameter from 'int' to 'int &&'

I am running all code below in VS 2015 Community Edition.

I am getting error in my code when I attempt to implement a suggestion that was suggested to me in Code Review. The part that I am having trouble with is changing the arguments to

TryPush
to be
TryPush(T&& val)
.

#pragma once

#include <atomic>
#include <memory>


template <typename T> class RingBuffer {
public:

/*
Other functions
*/

void Push(T val) {
while (!TryPush(val));
}

private:

/*
Other functions
*/

//Private Member Functions
bool TryPush(T && val) {
const std::size_t current_write = write_position.load(std::memory_order_acquire);
const std::size_t current_read = read_position.load(std::memory_order_acquire);
const std::size_t next_write = increment_index(current_write);

if (next_write == current_read) { return false; }

_ring_buffer_array[current_write] = std::move(val);
write_position.store(next_write, std::memory_order_release);

return true;
}

std::size_t increment_index(std::size_t index) {
return (index + 1) % _buffer_capacity;
}

//Private Member Variables
std::atomic<std::size_t> read_position = 0;
std::atomic<std::size_t> write_position = 0;

std::size_t _buffer_capacity;
std::unique_ptr<T[], RingBufferFree> _ring_buffer_array;
};


Whenever I attempt to compile this code I get the following error bool RingBuffer::TryPush(T &&)': cannot convert argument 1 from 'int' to 'int &&. What confuses me is that if change the code to

#pragma once

#include <atomic>
#include <memory>


template <typename T> class RingBuffer {
public:

/*
Other functions
*/

void Push(T && val) {
while (!TryPush(val));
}

private:

/*
Other functions
*/

//Private Member Functions
bool TryPush(T val) {
const std::size_t current_write = write_position.load(std::memory_order_acquire);
const std::size_t current_read = read_position.load(std::memory_order_acquire);
const std::size_t next_write = increment_index(current_write);

if (next_write == current_read) { return false; }

_ring_buffer_array[current_write] = std::move(val);
write_position.store(next_write, std::memory_order_release);

return true;
}

std::size_t increment_index(std::size_t index) {
return (index + 1) % _buffer_capacity;
}

//Private Member Variables
std::atomic<std::size_t> read_position = 0;
std::atomic<std::size_t> write_position = 0;

std::size_t _buffer_capacity;
std::unique_ptr<T[], RingBufferFree> _ring_buffer_array;
};


It compiles and runs. I was under the impressions from Scott Meyer's blog post that
TryPush(T && val)
is a Universal reference and I should be able to use it as is shown in the first code snippet and then move the value into the array thus ensuring the code works regardless of whether an lvalue or rvalue is passed into the function. It seems to work if it the public facing
Push
method and thus I am sort of confused as to what is going on. I must be missing something here and was wondering if anybody could clarify what exactly it is. Thanks.

Edit
Called like so

RingBuffer<int> r(50);
for (int i = 0; i < 20; i++) {
r.Push(i + 1);
}

M.M M.M
Answer

There are no universal references in your code. In the blog post you linked, see this similar example:

template <class T, class Allocator = allocator<T> >
class vector {
public:
    ...
    void push_back(T&& x);       // fully specified parameter type ⇒ no type deduction;
    ...                          // && ≡ rvalue reference
};

To use this code you would write something like vector<int> v; v.push_back(x); , and the function is already known as taking int&& so there is no deduction.

Universal references only happen when the template type is being deduced from the argument (and they work because the type can be deduced as a reference type).


To make your code work, you could provide lvalue-reference and rvalue-reference overloads for TryPush. Although your original attempt with pass-by-value would be fine , if you change TryPush(val) to TryPush(std::move(val)).

Comments