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?
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.
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.
C++ empty() function is used to check if the forward list container is empty or not.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With