Вероятно, лучшее из того, что стоит сделать, это просто a.emplace_back();
.
Ваши push_back
требования также не имеют правильного синтаксиса. Я думаю, вы хотите:
template <class ContainerType>
concept PushBackContainer = requires(
ContainerType& a,
typename ContainerType::value_type const& cv,
typename ContainerType::value_type& v)
{
requires SequenceContainer<ContainerType>;
a.push_back(cv);
a.push_back(std::move(v));
a.emplace_back();
};
Требования не проверяют подпись функции; они проверяют правильность выражения (без создания большего количества шаблонов, чем необходимо). Если бы у нас был такой класс, как:
class StrangeContainer {
public:
using value_type = std::string;
using const_reference = const value_type&;
private:
struct ValueHolder {
ValueHolder(const std::string& s) : value(s) {}
ValueHolder(std::string&& s) : value(std::move(s)) {}
std::string value;
};
public:
void push_back(ValueHolder);
template <typename ... Args>
void emplace_back(Args&&...);
};
, тогда игнорирование SequenceContainer
требований, PushBackContainer<StrangeContainer>
было бы истинным, и это также удовлетворяло бы собственным требованиям Стандарта, связанным с push_back
. Он удовлетворяет техническим требованиям, хотя и имеет некоторые неожиданные эффекты, такие как тот факт, что push_back("")
является плохо сформированным.
Так что для push_back
мы действительно просто проверяем, что его можно вызвать с помощью const
lvalue и с ненулевым значением const
. (Стандарт также требует, чтобы он вызывался не со значением const
l и со значением const
r, и эти случаи имеют то же поведение, что и при вызове со значением const
l.)
(Если вы действительно хотите проверить на точную push_back
подпись, вы можете попробовать static_cast<void (ContainerType::*)(typename ContainerType::value_type&&)>(&ContainerType::push_back);
- но это не рекомендуется, поскольку функции-члены в пространстве имен std
не обязательно должны иметь подписи точно так, как описано, только для того, чтобы вызывается с теми же аргументами, что и при объявлении, как описано.)
Кроме того, стандартные шаблоны классов контейнеров не имеют никаких ограничений для своих функций push_back
или emplace_back
. Каждый экземпляр шаблона, который имеет push_back
, объявляет обе перегрузки, независимо от того, является ли тип копируемым и / или подвижным. В противном случае было бы ошибкой фактически вызывать или иным образом использовать функцию push_back
, но она «существует» для целей выражений require-выражений и контекстов SFINAE. Точно так же шаблон элемента emplace_back
объявляется для принятия любого количества аргументов с любыми типами и категориями значений, независимо от того, могут ли они использоваться в качестве value_type
аргументов конструктора или нет.
Итак, что бы мы хотели чтобы проверить, чтобы выяснить, есть ли в контейнере emplace_back
с по существу обычной декларацией функции variadi c, его необходимо сформулировать следующим образом: можно ли вызывать emplace_back
с любым числом аргументов, причем каждый из них имеет любой возможный тип, а каждый быть или lvalue или rvalue? Я не думаю, что есть какой-то способ ответить на этот вопрос в C ++, используя выражения-требования, трюки SFINAE или другие. Так что я бы просто сделал один простой тест на существование какого-то типа emplace_back
, и этот тест мог бы быть как можно более простым: ноль аргументов.
Вы могли бы стать более любопытным, а также проверить некоторые дополнительные случаи. : emplace_back
принимает различное количество аргументов, вплоть до некоторого фиксированного максимума? Принимает ли он аргументы lvalue и rvalue? Принимает ли он аргументы типа фиктивных struct
? Фиктивные struct
типы, которые не являются MoveConstructible? const
, volatile
и const volatile
типов? Все возможные комбинации всего вышеперечисленного? Но поскольку вы никогда не будете охватывать все случаи, какую ценность действительно дает каждое частичное улучшение по сравнению с усилиями, сложностью и обслуживанием, необходимыми для добавления проверок?