std:forward внутри класса шаблона

 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::push мог обрабатывать как ссылку rvalue, так и ссылку lvalue объекта типа T, чтобы перенаправить его на std::queue::push правильную версию. Что предпочтительнее сделать, как в приведенном выше коде, или предоставить две версии метода push внутри моего класса BlockingQueue? Один для lvalue и один для rvalue


person Guillaume07    schedule 01.11.2013    source источник
comment
Я бы написал две перегрузки, одну для T const & и одну для T &&...   -  person Kerrek SB    schedule 01.11.2013
comment
Вышеприведенное называется идеальной переадресацией, и оно прекрасно обрабатывает как lvalue, так и rvalue. Я бы так и оставил. (@KerrekSB Хм, значит ли это, что этот вопрос в первую очередь основан на мнении? ;)   -  person Daniel Frey    schedule 01.11.2013
comment
@DanielFrey: называйте это как хотите, но если вы объедините вывод аргумента с немедленным ограничением is_same для самого типа, я думаю, вы пытаетесь быть слишком умным. Если вам нужен T, просто сделайте перегрузку для T. Он короче и легче для чтения и диагностики.   -  person Kerrek SB    schedule 01.11.2013
comment
@KerrekSB На самом деле это правда, я не обратил внимания на этот static_assert, так как мой разум просто пропустил его за ненадобностью, и я бы удалил его.   -  person Daniel Frey    schedule 01.11.2013
comment
@KerrekSB: я полностью согласен с вашим аргументом, и именно в этом смысл моего поста.   -  person Cassio Neri    schedule 01.11.2013
comment
static_assert кажется неправильным. Разве вы не хотите поддерживать константные значения? То, что вы действительно должны проверить, это is_convertible<U,T>::value. Другой способ справиться с этим - enable_if вместо статического утверждения. Преимущество enable_if заключается в том, что вы можете удалить функцию довольно рано из набора перегрузок, чтобы избежать двусмысленностей и вместо этого позволить компилятору указать на вызывающий нарушение пользовательский код.   -  person sellibitze    schedule 04.11.2013
comment
@sellibitze: вы правы, но, как говорят другие, предпочтительнее иметь две версии функции push (одну для rvalue и одну для lvalue)   -  person Guillaume07    schedule 05.11.2013


Ответы (2)


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

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

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

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

BlockingQueue<foo> b;

С кодом OP (*)

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_lists), то лучше предоставить две разные перегрузки.

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

typename std::remove_reference<U>::type>::value
^^^^^^^^
person Cassio Neri    schedule 01.11.2013
comment
open-std.org/JTC1/SC22/WG21/docs/papers/2008/ еще одна причина предоставлять разные реализации для lvalue и rvalue? - person Vivian Miranda; 02.11.2013
comment
@ViniciusMiranda: проблема, упомянутая в документе, восходит к 2008 году, когда семантика перемещения еще разрабатывалась. Прочитав статью, я пришел к выводу, что тогда ссылки на rvalue могли быть привязаны к lvalue. В документе предлагается запретить эту привязку, что и было принято в C++11. Так что этой опасности больше не существует. Моя основная проблема (могут быть и другие, которых я не вижу) с единственной реализацией, использующей универсальную ссылку, заключается, как я сказал в посте, в невозможности вывести тип braced-init-lists потому что у них нет типа (это просто синтаксические конструкции). - person Cassio Neri; 04.11.2013

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

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

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

person zahir    schedule 01.11.2013