Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can std::forward_list members be implemented as static?

std::forward_list provides insert_after and erase_after members which may not need to actually access the std::forward_list object. Therefore they can be implemented as static member functions and be called without a list object — useful for an object that wants to delete itself from a list, which is a very common use. EDIT: This optimization only applies to forward_list specializations on std::allocator or user-defined stateless allocators.

Can a standard-conforming implementation do this?

§17.6.5.5/3 says

A call to a member function signature described in the C++ standard library behaves as if the implementation declares no additional member function signatures.

with a footnote

A valid C++ program always calls the expected library member function, or one with equivalent behavior. An implementation may also define additional member functions that would otherwise not be called by a valid C++ program.

It's not clear to me whether adding static would create a "different" member function, but removing an (implicit) argument shouldn't break anything that adding defaulted arguments wouldn't, and that is legal. (You cannot legally take a PTMF to any standard member function.)

It strikes me that the library should be allowed to do this, but I'm not sure if some rule would be broken. And how normative are the listed member function prototypes?

like image 563
Potatoswatter Avatar asked Oct 19 '11 12:10

Potatoswatter


People also ask

What is std :: Forward_list?

std::forward_list is a container that supports fast insertion and removal of elements from anywhere in the container. Fast random access is not supported. It is implemented as a singly-linked list. Compared to std::list this container provides more space efficient storage when bidirectional iteration is not needed.

How to add to forward list in c++?

push_front(): This function is used to insert the element at the first position on forward list. The value from this function is copied to the space before first element in the container. The size of forward list increases by 1.

How do I find out if a forward list is empty?

C++ empty() function is used to check if the forward list container is empty or not.


1 Answers

The standard says you can get away with it if no one can tell the difference. And you are correct that one can not legally create a PTMF into forward_list, so you're safe that way.

The danger of custom allocators has already been pointed out. But even for std::allocator<T> there is a danger that someone could specialize std::allocator<MyType> and then detect that the allocator::construct/destroy wasn't being called.

Okay, but can one specialize say std::forward_list<int> (no custom allocator, no user defined value_type) and make insert_after static?

No. This change would be detectable with the new SFINAE capabilities. Here is a demo:

#include <memory>
#include <iostream>

template <class T, class A = std::allocator<T>>
class forward_list
{
public:
    typedef T value_type;
    struct const_iterator {};
    struct iterator {};

    iterator insert_after(const_iterator p, const T& x);
};

template <class C>
auto test(C& c, typename C::const_iterator p, const typename C::value_type& x)
    -> decltype(C::insert_after(p, x))
{
    std::cout << "static\n";
    return typename C::iterator();
}

template <class C>
auto test(C& c, typename C::const_iterator p, const typename C::value_type& x)
    -> decltype(c.insert_after(p, x))
{
    std::cout << "not static\n";
    return typename C::iterator();
}

int main()
{
    ::forward_list<int> c;
    test(c, ::forward_list<int>::const_iterator(), 0);
}

This program runs and prints out:

not static

But if I make insert_after static:

static iterator insert_after(const_iterator p, const T& x);

Then I get a compile time error:

test.cpp:34:5: error: call to 'test' is ambiguous
    test(c, ::forward_list<int>::const_iterator(), 0);
    ^~~~
test.cpp:16:6: note: candidate function [with C = forward_list<int, std::__1::allocator<int> >]
auto test(C& c, typename C::const_iterator p, const typename C::value_type& x)
     ^
test.cpp:24:6: note: candidate function [with C = forward_list<int, std::__1::allocator<int> >]
auto test(C& c, typename C::const_iterator p, const typename C::value_type& x)
     ^
1 error generated.

Difference detected.

Thus it is non-conforming to make forward_list::insert_after static.

Update

If you want to make the "static" overload callable, you simply need to make it slightly more desirable than the "not static" overload. One way of doing that is changing the "not static" overload to:

template <class C, class ...Args>
auto test(C& c, typename C::const_iterator p, const typename C::value_type& x, Args...)
    -> decltype(c.insert_after(p, x))
{
    std::cout << "not static\n";
    return typename C::iterator();
}

Now the test will print out either "static" or "not static" depending on whether the insert_after member function is static or not.

like image 180
Howard Hinnant Avatar answered Oct 19 '22 23:10

Howard Hinnant