Как вы определяете emplace_back и другие шаблонные функции variadi c в концепции C ++? - PullRequest
1 голос
/ 02 марта 2020

Я пытаюсь определить концепцию C ++ для контейнеров стандартной библиотеки, которые позволяют push_back / emplace_back:

template <class ContainerType>
concept PushBackContainer = requires(ContainerType a)
{
    requires SequenceContainer<ContainerType>;
    { a.push_back(typename ContainerType::const_reference& v) };
    { a.push_back(typename ContainerType::value_type&& v) };
    // How do you define a variable templated function: 
    { template< class... Args > a.emplace_back(Args&&... args) };
}

Проблема, с которой я столкнулся, заключается в том, как определить emplace_back с его аргументами шаблона variadi c? Я использую Visual Studio 2019, но если это не поддерживается, меня заинтересует правильный синтаксис, когда он настанет.

1 Ответ

1 голос
/ 03 марта 2020

Вероятно, лучшее из того, что стоит сделать, это просто 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 типов? Все возможные комбинации всего вышеперечисленного? Но поскольку вы никогда не будете охватывать все случаи, какую ценность действительно дает каждое частичное улучшение по сравнению с усилиями, сложностью и обслуживанием, необходимыми для добавления проверок?

...