I am making some sort of container and I would like to mimic the interface of an std::vector. However, I am having a hard time understanding how the constructor overload (4) works. The problem is that it normally conflicts with overload (2).
// (2)
vector(size_type count, const T& value, const Allocator& alloc = Allocator());
// (4)
template<class InputIt>
vector(InputIt first, InputIt last, const Allocator& alloc = Allocator());
According to cppreference, until C++11 :
this constructor has the same effect as overload (2) if InputIt is an integral type.
I understand how it was done (tag dispatching or template specialization, I suppose), but I have no idea how the new behavior is achieved in C++11 :
This overload only participates in overload resolution if InputIt satisfies InputIterator, to avoid ambiguity with the overload (2).
Is that some sort of SFINAE trick ? I do not understand how SFINAE could work here. And, since concepts or not a thing in C++11 (nor C++14), I have no idea about how I could do that for my container.
Hence my question : how is it done in the standard library (or at least a guess), and how can I do it relatively easily for my container ?
The way it's currently worded in the standard is rather interesting. [sequence.reqmts]/p15:
The extent to which an implementation determines that a type cannot be
an input iterator is unspecified, except that as a minimum integral
types shall not qualify as input iterators.
In other words, it's sufficient for implementations to test for integral types only, but they can do more if they want to.
With a SFINAE-friendly std::iterator_traits (voted into the C++17 working paper as an LWG issue, but probably provided by most implementations all along anyway), for example, one might test that std::iterator_traits<InputIterator>::iterator_category is valid and denotes a type derived from std::input_iterator_tag with something like
template<class InputIt, std::enable_if_t<std::is_base_of<std::input_iterator_tag,
typename std::iterator_traits<InputIterator>::iterator_category>::value, int> = 0>
vector(InputIt first, InputIt last, const Allocator& alloc = Allocator());
Note that this is just a proof-of-concept. Real implementations in standard libraries would probably be 1) more complex (for example, it may optimize based on iterator category - for forward iterators or better, it can allocate memory for all the elements in one go) and 2) even uglier than this and filled with underscores to avoid name conflicts.
Related
In C++20 we got concepts for iterator categories, e.g. std::forward_iterator corresponds to named requirement ForwardIterator.
They are not equivalent. In some (but not all) regards, the concepts are weaker:
(1) "Unlike the [ForwardIterator or stricter] requirements, the [corresponding] concept does not require dereference to return an lvalue."
(2)
"Unlike the [InputIterator] requirements, the input_iterator concept does not require equality_comparable, since input iterators are typically compared with sentinels."
...
The new std::ranges algorithms seem to use the concepts to check iterator requirements.
But what about the other standard algorithms? (that existed pre-C++20)
Do they still use the same iterator requirements in C++20 as they were pre-C++20, or did the requirements for them get relaxed to match the concepts?
Do they still use the same iterator requirements in C++20 as they were pre-C++20, or did the requirements for them get relaxed to match the concepts?
The existing std:: algorithms still use the named requirements. For instance, [alg.find] has both:
template<class InputIterator, class T>
constexpr InputIterator find(InputIterator first, InputIterator last,
const T& value);
and
template<input_iterator I, sentinel_for<I> S, class T, class Proj = identity>
requires indirect_binary_predicate<ranges::equal_to, projected<I, Proj>, const T*>
constexpr I ranges::find(I first, S last, const T& value, Proj proj = {});
template<input_range R, class T, class Proj = identity>
requires indirect_binary_predicate<ranges::equal_to, projected<iterator_t<R>, Proj>, const T*>
constexpr borrowed_iterator_t<R>
ranges::find(R&& r, const T& value, Proj proj = {});
The same is true for all the algorithms.
Note that there is a proposal to change this: Ranges views as inputs to non-Ranges algorithms. That paper would change the named requirements to become in line with the concepts (note that it would not change the std:: algorithms to accept sentinels).
Consider the following:
using vector_type = std::vector<int>;
using const_iterator = typename vector_type::const_iterator;
using const_reverse_iterator = typename vector_type::const_reverse_iterator;
using iterator = typename vector_type::iterator;
using reverse_iterator = typename vector_type::reverse_iterator;
int main()
{
static_assert(!std::is_assignable_v<iterator, const_iterator>); // passes
static_assert(!std::is_assignable_v<reverse_iterator, const_reverse_iterator>); // fails
static_assert(std::is_assignable_v<reverse_iterator, const_reverse_iterator>); // passes
}
I can check that assignment of iterator{} = const_iterator{} is not valid, but not an assignment of reverse_iterator{} = const_reverse_iterator{} with this type trait.
This behavior is consistent across gcc 9.0.0, clang 8.0.0, and MSVC 19.00.23506
This is unfortunate, because the reality is that reverse_iterator{} = const_reverse_iterator{} doesn't actually compile with any of the above-mentioned compilers.
How can I reliably check such an assignment is invalid?
This behavior of the type trait implies that the expression
std::declval<reverse_iterator>() = std::declval<const_reverse_iterator>()
is well formed according to [meta.unary.prop], and this appears consistent with my own attempts at an is_assignable type trait.
This trait passes because the method exists that can be found via overload resolution and it is not deleted.
Actually calling it fails, because the implementation of the method contains code that isn't legal with those two types.
In C++, you cannot test if instantiating a method will result in a compile error, you can only test for the equivalent of overload resolution finding a solution.
The C++ language and standard library originally relied heavily on "well, the method body is only compiled in a template if called, so if the body is invalid the programmer will be told". More modern C++ (both inside and outside the standard library) uses SFINAE and other techniques to make a method "not participate in overload resolution" when its body would not compile.
The constructor of reverse iterator from other reverse iterators is the old style, and hasn't been updated to "not participate in overload resolution" quality.
From n4713, 27.5.1.3.1 [reverse.iter.cons]/3:
template<class U> constexpr reverse_iterator(const reverse_iterator<U>& u);
Effects: Initializes current with u.current.
Notice no mention of "does not participate in overload resolution" or similar words.
The only way to get a trait you want like this would be to
Change (well, fix) the C++ standard
Special case it
I'll leave 1. as an exercise. For 2., you know that reverse iterators are templates over forward iterators.
template<class...Ts>
struct my_trait:std::is_assignable<Ts...> {};
template<class T0, class T1>
struct my_trait<std::reverse_iterator<T0>, std::reverse_iterator<T1>>:
my_trait<T0, T1>
{};
and now my_trait is is_assignable except on reverse iterators, where it instead tests assignability of the contained iterators.
(As extra fun, a reverse reverse iterator will work with this trait).
I once had to do something very similar with std::vector<T>::operator<, which also blindly called T<T and didn't SFINAE disable it if that wasn't legal.
It may also be the case that a C++ standard library implementation can make a constructor which would not compile not participate in overload resolution. This change could break otherwise well formed programs, but only by things as ridiculous as your static assert (which would flip) or things logically equivalent.
It's just a question of constraints. Or lack thereof. Here's a reduced example with a different trait:
struct X {
template <typename T>
X(T v) : i(v) { }
int i;
};
static_assert(is_constructible_v<X, std::string>); // passes
X x("hello"s); // fails
Whenever people talk about being SFINAE-friendly - this is fundamentally what they're referring to. Ensuring that type traits give the correct answer. Here, X claims to be constructible from anything - but really it's not. is_constructible doesn't actually instantiate the entire construction, it just checks the expression validity - it's just a surface-level check. This is the problem that enable_if and later Concepts are intended to solve.
For libstdc++ specifically, we have:
template<typename _Iter>
_GLIBCXX17_CONSTEXPR
reverse_iterator(const reverse_iterator<_Iter>& __x)
: current(__x.base()) { }
There's no constraint on _Iter here, so is_constructible_v<reverse_iterator<T>, reverse_iterator<U>> is true for all pairs T, U, even if it's not actually constructible. The question uses assignable, but in this case, assignment would go through this constructor template, which is why I'm talking about construction.
Note that this is arguably a libstdc++ bug, and probably an oversight. There's even a comment that this should be constrained:
/**
* A %reverse_iterator across other types can be copied if the
* underlying %iterator can be converted to the type of #c current.
*/
I'm wondering why std::raw_storage_iterator does not have an accompanying std::make_raw_storage_iterator like std::move_iterator and std::make_move_iterator. The std::raw_storage_iterator class template has two template parameters. So I think it is actually more useful to provide a make function for std::raw_storage_iterator than std::move_iterator which only has a single template parameter to specify.
With #dyps suggestions, a sensible implementation is indeed possible:
template <typename OutputIt>
auto make_raw_storage_iterator( OutputIt out ) {
return std::raw_storage_iterator<OutputIt, std::remove_pointer_t<decltype(&*out)>>(out);
}
(Demo with cppreference's example)
This is guaranteed to work since §20.7.10/1, which imposes requirements for the template arguments of raw_storage_iterator, mandates that:
OutputIterator is required to have its operator* return an object for
which operator& is defined and returns a pointer to T [..]
Where T is the second argument, the value type of the output range. So, given the underlying output iterator, we have enough information to determine the intended specialization of raw_storage_iterator.
The reason this has not been proposed is assuredly not more than an oversight - consider that make_reverse_iterator or even make_unique have not been provided before C++14, either. Feel free to take the first step.
vector<T> has a constructor that takes the size of the vector, and as far as I know it is explicit, which can be proved by the fact that the following code fails to compile
void f(std::vector<int> v);
int main()
{
f(5);
}
What I cannot understand and am asking you to explain is why the following code compiles
std::vector<std::vector<int>> graph(5, 5);
Not only does it compile, it actually resizes graph to 5 and sets each element to a vector of five zeros, i.e. does the same as would the code I would normally write:
std::vector<std::vector<int>> graph(5, std::vector<int>(5));
How? Why?
Compiler: MSVC10.0
OK, seems it's an MSVC bug (yet another one). If someone can elaborate on the bug in an answer (i.e. summarize the cases where it is reproduced) I would gladly accept it
It is not really a bug. The question is what could go wrong to allow the second piece of code while the first does not compile?
The issue is that while it seems obvious to you what constructor you want to call when you do:
std::vector<std::vector<int>> graph(5, 5);
it is not so clear for the compiler. In particular there are two constructor overloads that can potentially accept the arguments:
vector(size_type,const T& value = T());
template <typename InputIterator>
vector(InputIterator first, InputIterator last);
The first one requires the conversion of 5 to size_type (which is unsigned), while the second is a perfect match, so that will be the one picked up by the compiler...
... but the compiler requires that the second overload, if the deduced type InputIterator is integral behaves as if it was a call to:
vector(static_cast<size_type>(first),static_cast<T>(last))
What the C++03 standard effectively mandates is that the second argument is explicitly converted from the original type int to the destination type std::vector<int>. Because the conversion is explicit you get the error.
The C++11 standard changes the wording to use SFINAE to disable the iterator constructor if the argument is not really an input iterator, so in a C++11 compiler the code should be rejected (which is probably the reason some have claimed this to be a bug).
To me it looks like it's calling this constructor:
template <class InputIterator>
vector (InputIterator first, InputIterator last,
const allocator_type& alloc = allocator_type());
I'm not sure where explicit comes into it, because the constructor takes multiple parameters. It's not auto casting from an int to a vector.
This is actually an extension, not a bug.
The constructor being invoked is the one that takes two iterators (but really, the signature will match any two parameters of the same type); it then invokes a specialization for when the two iterators are actually int, which explicitly constructs a value_type using the value of end and populates the vector with begin copies of it.
I am writing a new container, and trying to comply with N3485 23.2.3 [sequence.reqmts]/14, which states:
For every sequence container defined in this Clause and in Clause 21:
If the constructor
template <class InputIterator>
X(InputIterator first, InputIterator last,
const allocator_type& alloc = allocator_type())
is called with a type InputIterator that does not qualify as an
input iterator, then the constructor shall not participate in overload
resolution.
(/14 repeats this almost verbatim for the member functions taking iterator ranges)
N3485 23.2.3 [sequence.reqmts]/15 says:
The extent to which an implementation determines that a type cannot be an input iterator is unspecified, except that as a minimum integral types shall not qualify as input iterators.
My understanding is that the phrase "shall not participate in overload resolution" means that the container implementer is supposed to use SFINAE tricks to disable that constructor or member function during template argument deduction. For the member functions, this is no big deal; as the return type on a function is the normal way to use enable_if. But for the constructor, there is no return type on which enable_if can be applied. This was my first attempt to declare the constructor:
// The enable_if use below is to comply with 23.2.3 [sequence.reqmts]/14:
// ... is called with a type InputIterator that does not qualify as an input iterator
// then the constructor shall not participate in overload resolution.
template <typename InputIterator>
path(std::enable_if<!std::is_integral<InputIterator>::value, InputIterator>::type first,
InputIterator last, Allocator const& allocator = allocator_type());
However, boost's enable_if docs suggest using a dummy pointer parameter initialized to nullptr instead of using an actual parameter to a function. Is that necessary for correct behavior here or is the preceding declaration of path's iterator range constructor okay?
The Good Robot (R. Martinho Fernandes) discusses the issue with a clean C++11 solution, namely using a default template parameter to apply enable_if, in his blog.
However, let me just point out here that doing
template< class Type >
void foo( typename Something< Type >::T )
foils argument deduction.
It's still possible to call the function, by explicitly providing the template argument. But in C++ the compiler will simply refuse to match e.g. a MyType actual argument to formal argument type Something<Blah>::T, because while this can be done in some special cases it cannot always be done (there could be countless choices of Blah where Something<Blah>::T was MyType).
So, your current approach won't work in general, but the whole problem of the specification's requirement is a C++11 problem, so the C++11 specific solution is OK! :-)