简体   繁体   中英

How do you define emplace_back and other variadic template functions in a C++ concept?

I'm trying to define a C++ concept for standard library containers that allow 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) };
}

The problem I have is how do I define emplace_back with its variadic template arguments? I'm using Visual Studio 2019 but if this isn't supported I'd be interested in the correct syntax come the time it is.

Probably about the best that's worth doing is just a.emplace_back(); .

Your push_back requirements don't have a correct syntax, either. I think you want:

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();
};

Requirements don't check for a function signature; they check for the validity of an expression (without instantiating more templates than necessary). If we had a class like:

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&&...);
};

then ignoring SequenceContainer requirements, PushBackContainer<StrangeContainer> would be true, and it would also satisfy the Standard's own requirements related to push_back . It satisfies the technical requirements, even though it has some surprising effects like the fact that push_back("") is ill-formed.

So for push_back , we're really just checking that it can be called with a const lvalue and with a non- const rvalue. (The Standard actually also requires that it can be called with a non- const lvalue and with a const rvalue, and these cases have the same behavior as when called with a const lvalue.)

(If you really wanted to test for an exact push_back signature, you could try static_cast<void (ContainerType::*)(typename ContainerType::value_type&&)>(&ContainerType::push_back); - but this is not recommended, since member functions in namespace std are not required to have signatures exactly as described, only to be callable with the same arguments as if declared as described.)

Also, the standard container class templates don't have any constraints on their push_back or emplace_back functions. Every instantiation of the templates which have push_back declares both overloads, whether or not the type is copyable and/or movable. If not, it would be an error to actually call or otherwise odr-use the push_back function, but it "exists" for purposes of requires-expressions and SFINAE contexts. Likewise, the emplace_back member template is declared to accept any number of arguments with any types and value categories, no matter whether they can be used as value_type constructor arguments or not.

So what we would want to test to find out if the container has an emplace_back with an essentially ordinary variadic function declaration would need to be phrased as: Can emplace_back be called with any number of arguments, with each having any possible type and each being either an lvalue or rvalue? I don't think there's any way to really answer that within C++, using requires-expressions, SFINAE tricks, or otherwise. So I would just do one simple test for existence of some sort of emplace_back , and that test might as well be as simple as possible: zero arguments.

You could get fancier and also test for some additional cases: Does emplace_back accept different numbers of arguments, up to some fixed maximum? Does it accept lvalue and rvalue arguments? Does it accept arguments of dummy struct types? Dummy struct types that aren't MoveConstructible? const , volatile , and const volatile types? All possible combinations of all of the above? But since you'll never cover all the cases, how much value does each partial enhancement like this really give, compared to the effort, complexity, and maintenance needed to add checks?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM