2013-11-01 5 views
6
template<typename T> 
    class BlockingQueue 
    { 
     std::queue<T> container_; 

     template< typename U > 
     void push(U&& value) 
     { 
      static_assert(std::is_same<T, typename std::remove_reference<U>::type>::value,"Can't call push without the same parameter as template parameter's class"); 

      container_.push(std::forward<U>(value)); 
     } 
}; 

Я хотел бы BlockingQueue :: толчок способ быть в состоянии обрабатывать как RValue и Lvalue ссылку на объект типа T, чтобы направить его std::queue::push правильной версии. Предпочтительно ли делать код выше, или предоставить две версии метода push внутри моего класса BlockingQueue? Один для lvalue и один для rvalueстанд: вперед внутри шаблона класса

+1

Я бы написал две перегрузки, одну для 'T const &' и одну для 'T &&' ... –

+0

Вышеупомянутая функция называется «совершенной переадресацией», и она отлично обрабатывает как lvalues, так и rvalues. Я бы так и сделал. (@KerrekSB Hm, означает ли это, что это Q, в основном, основано на мнениях?;) –

+0

@ DanielFrey: назовите его, как хотите, но если вы объедините вывод аргумента с непосредственным ограничением 'is_same' на самом типе, я думаю, вы пытаетесь быть слишком умным. Если вы хотите 'T', просто сделайте перегрузки для' T'. Это короче и легче читать и диагностировать. –

ответ

7

Реализация кажется правильной для меня и выполняет эту работу.

Тем не менее, предоставление различных реализаций для lvalues ​​и rvalues ​​может быть хорошей идеей в вашем случае. Основная причина (что я могу думать) заключается в том, что вывод аргументов типа шаблона не работает для braced-init-lists. Рассмотрим:

struct foo { 
    foo(std::initializer_list<int>) { 
    } 
}; 

// ... 
foo f{1, 2, 3}; // OK 

BlockingQueue<foo> b; 

С кодом ФП в (*)

b.push(f);   // OK 
b.push({1, 2, 3}); // Error 

Если вместо этого, следующие перегруженные BlockingQueue::push предусмотрены:

void push(const T& value) { 
    container_.push(value); 
} 

void push(T&& value) { 
    container_.push(std::move(value)); 
} 

Затем линия, которая используется для неудачи будет работать хорошо.

Те же аргументы применимы к агрегатам. Например, если foo был определен как

struct foo { 
    int a, b, c; 
}; 

можно было бы наблюдать за одни и те же модели поведения, описанные выше.

Мое заключение состоит в том, что если вы хотите, чтобы BlockingQueue поддерживал больше типов (включая агрегаты или типы с конструкторами, принимающими std::initializer_list), тогда лучше обеспечить две разные перегрузки.

(*) Небольшая поправка в коде OP в: в static_assert вам нужно использовать typename

typename std::remove_reference<U>::type>::value 
^^^^^^^^ 
+0

Является http://www.open-std.org/JTC1/SC22/WG21/ docs/papers/2008/n2812.html # impact-on-the-standard-library Другая причина для предоставления различной реализации lvalue и rvalues? –

+1

@ViniciusMiranda: Проблема, упомянутая в документе, относится к 2008 году, когда семантика перемещения все еще разрабатывалась. Читая бумагу, я понял, что тогда ссылки rvalue могут быть связаны с lvalues. В документе предлагается запретить эту привязку, и это было принято на C++ 11. Значит, этой опасности больше не существует. Моя главная проблема (могут быть другие, которых я не вижу) с единственной реализацией, использующей универсальную ссылку, как я сказал в сообщении, невозможность вывести тип * braced-init-lists * t имеют тип (это просто синтаксические контракты). –

0

Если вы хотите использовать идеальную переадресацию, то я предлагаю вам использовать emplace метод queue класса. emplace метод пересылает данный аргумент в T конструктор. Не нужно будет проверять, является ли T таким же, как U. Он должен компилироваться до тех пор, пока T можно построить с U. Кроме того, вы можете использовать вариационные аргументы шаблона, если хотите.

template<typename... Args> 
void push(Args&&... args) 
{ 
    container_.emplace(std::forward<Args>(args)...); 
} 

Таким образом, вы можете нажимать все, что хотите, до тех пор, пока T можно построить из данных аргументов.

Смежные вопросы