I am currently looking at some code that can be compiled on newer versions of GCC but not on older ones. In my case I am using a std::back_inserter to std::copy some data from one data structure to a custom data structure. If I forget the typedef value_type & const_reference typedef in this custom data structure however, this will not compile on GCC 4.4. The same code compiles and runs just fine on GCC 4.5.
What is the difference in between these two compiler versions, that makes the code compile on one version but not on the other. I would guess it has something to do with the implementation of C++11 which was much less complete in GCC 4.4. Probably something with decltype or another new C++11 keyword, I would guess.
Also is this code correct, if I use the std::back_inserter without defining the const_reference type? I usually thought that one has to implement the full set of typedefs (value_type, reference, const_reference etc) in order to be compatible with the STL-algorithms library? Or can I safely assume that if my code compiles in this case I am not invoking anything dangerous (e.g. move semantics, which would destroy my other datastructure).
The standard (1998) says that std::back_insert_iterator needs Container::const_reference. In "24.4.2.1 Template class back_insert_iterator", [lib.back.insert.iterator], it says:
back_insert_iterator<Container>&
operator=(typename Container::const_reference value);
The 2011 standard only wants Container::value_type,
back_insert_iterator<Container>&
operator=(const typename Container::value_type& value);
back_insert_iterator<Container>&
operator=(typename Container::value_type&& value);
So, to be compatible with both versions of the C++ standard, define both value_type and const_reference_type.
In both GCC 4.4.6 and 4.5.1, the definition of operator= is identical (libstdc++-v3/include/bits/stl_iterator.h):
back_insert_iterator&
operator=(typename _Container::const_reference __value)
{
container->push_back(__value);
return *this;
}
and I get the same error with both compilers, perhaps you'll need to double check if you're using the correct compiler versions.
The reason that you need const_reference defined for your data-structure is because the assignment operator in GCC 4.4 for an lvalue argument type in the std::back_insert_iterator class is defined as:
template<class Container>
back_insert_iterator<Container>&
back_insert_iterator<Container>::operator=
(typename Container::const_reference value);
Thus const_reference needs to be a resolvable identifier in your class-type in to properly instantiate the assignment operator in the std::back_insert_iterator class template.
In GCC 4.5, this definition of the assignment operator for lvalue arguments has been changed to
template<class Container>
back_insert_iterator<Container>&
back_insert_iterator<Container>::operator=
(const typename Container::value_type& value);
in order to support the new C++11 specification. Since your code compiles correctly with GCC 4.5, I'm assuming you must have value_type properly defined for your data-structure.
Related
The following code compiles in C++11, C++14, and C++17, but does not compile in C++20. What change to the standard broke this code?
#include <vector>
#include <utility>
template<typename T>
struct bar
{
typename T::reverse_iterator x;
};
struct foo
{
bar<std::vector<std::pair<foo, foo>*>> x;
};
int main()
{
foo f;
}
The error is quite long, but can be summarized as:
template argument must be a complete class
This was always undefined. [res.on.functions]/2.5 says:
In particular, the effects are undefined in the following cases:
[...]
If an incomplete type ([basic.types]) is used as a template argument when instantiating a template component or evaluating a concept, unless specifically allowed for that component.
std::pair does not (and cannot) support incomplete types. You were just relying on order of instantiation to kind of get around that. Something changed in the library that slightly changed the evaluation order, leading to the error. But undefined behavior is undefined - it happened to work before and it happens to not work now.
As to why it's specifically C++20 that is causing this to fail. In C++20, iterators changed to have this new iterator_concept idea. In order to instantiate that, reverse_iterator needs to determine what the concept should be. That looks like this:
#if __cplusplus > 201703L && __cpp_lib_concepts
using iterator_concept
= conditional_t<random_access_iterator<_Iterator>,
random_access_iterator_tag,
bidirectional_iterator_tag>;
using iterator_category
= __detail::__clamp_iter_cat<typename __traits_type::iterator_category,
random_access_iterator_tag>;
#endif
Now, in the process of checking random_access_iterator, the root of iterator concept hierarchy is wonderfully named input_or_output_iterator, specified in [iterator.concept.iterator]:
template<class I>
concept input_or_output_iterator =
requires(I i) {
{ *i } -> can-reference;
} &&
weakly_incrementable<I>;
So, we have to do *i on our iterator type. That's __gnu_cxx::__normal_iterator<std::pair<foo, foo>**, std::vector<std::pair<foo, foo>*> > , in this case. Now, *i triggers ADL - because of course it does. And ADL requires instantiation of all the associated types - because those associated types might have injected friends that could be candidates!
This, in turn, requires instantiating pair<foo, foo> - because, we have to check. And then that ultimately fails in this specific case because instantiating a type requires instantiating all of the type's special member functions, and the way that libstdc++ implements conditional assignment for std::pair is using Eric Fisellier's trick:
_GLIBCXX20_CONSTEXPR pair&
operator=(typename conditional<
__and_<is_copy_assignable<_T1>,
is_copy_assignable<_T2>>::value,
const pair&, const __nonesuch&>::type __p)
{
first = __p.first;
second = __p.second;
return *this;
}
And is_copy_assignable requires complete types and we don't have one.
But really even if pair used concepts to check in this case, that would still involve instantiating the same type traits, so we'd ultimately end up in the same position.
Moral of the story is, undefined behavior is undefined.
I have code like this:
#include <vector>
#include <utility>
int main()
{
std::vector<bool> vb{true, false};
std::swap(vb[0], vb[1]);
}
Arguments about the sanity of vector<bool> aside, this was working just fine on:
Clang for Mac
Visual Studio for Windows
GCC for Linux
Then I tried building it with Clang on Windows and received the following error (abridged):
error: no matching function for call to 'swap'
std::swap(vb[0], vb[1]);
^~~~~~~~~
note: candidate function [with _Ty = std::_Vb_reference<std::_Wrap_alloc<std::allocator<unsigned int> > >, $1 = void] not viable: expects an l-value for 1st argument
inline void swap(_Ty& _Left, _Ty& _Right) _NOEXCEPT_COND(is_nothrow_move_constructible_v<_Ty>&&
I'm surprised that the results differ across implementations.
Why does it not work with Clang on Windows?
The standard doesn't require this to compile on any toolchain!
First recall that vector<bool> is weird and subscripting it gives you a temporary object of a proxy type called std::vector<bool>::reference, rather than an actual bool&.
The error message is telling you that it cannot bind this temporary to a non-const lvalue reference in the generic template <typename T> std::swap(T& lhs, T& rhs) implementation.
Extensions!
However, it turns out that libstdc++ defines an overload for std::swap(std::vector<bool>::reference, std::vector<bool>::reference), but this is an extension to the standard (or, if it is in there, I can't find any evidence for it).
libc++ does this too.
I'd guess that the Visual Studio stdlib implementation, which you're still using, doesn't, but then to add insult to injury you can bind temporaries to lvalue references in VS (unless you're using conformance mode), so the standard, "generic", std::swap function works until you substitute the VS compiler for the stricter Clang compiler.
As a result, you've been relying on extensions on all of the three toolchains for which it did work for you, and the Clang on Windows combination is the only one actually exhibiting strict compliance.
(In my opinion, those three toolchains should have diagnosed this so you didn't ship non-portable code this whole time. ๐)
What now?
It may be tempting to add your own specialisation of std::swap and std::vector<bool>::reference, but you're not allowed to do this for standard types; indeed, it would conflict with the overloads that libstdc++ and libc++ have chosen to add as extensions.
So, to be portable and compliant, you should change your code.
Perhaps a good old-fashioned:
const bool temp = vb[0];
vb[0] = vb[1];
vb[1] = temp;
Or make use of the special static member function that does exactly what you wanted:
std::vector<bool>::swap(vb[0], vb[1]);
Also spellable as follows:
vb.swap(vb[0], vb[1]);
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 trying to get a deeper understanding of C++ by reading the C++14 standard along with the source of libc++ and libstdc++. The implementation of various type_traits items varies between the two, particularly is_move_assignable, and I'm trying to figure out which of them is "more correct."
libc++:
template <class _Tp> struct is_move_assignable
: public is_assignable<typename add_lvalue_reference<_Tp>::type,
const typename add_rvalue_reference<_Tp>::type> {};
libstdc++:
template<typename _Tp, bool = __is_referenceable<_Tp>::value>
struct __is_move_assignable_impl;
template<typename _Tp>
struct __is_move_assignable_impl<_Tp, false>
: public false_type { };
template<typename _Tp>
struct __is_move_assignable_impl<_Tp, true>
: public is_assignable<_Tp&, _Tp&&>
{ };
template<typename _Tp>
struct is_move_assignable
: public __is_move_assignable_impl<_Tp>
{ };
The standard states:
For a referenceable type T, the same result as is_assignable<T&, T&&>::value, otherwise false.
The first thing I noted is that libc++ applies const to the second template parameter, which doesn't seem right since the move assignment operator takes a non-const rvalue. libstdc++ also uses __is_referenceable, which follows the wording of the standard, but libc++ doesn't. Is that requirement covered by libc++'s use of add_lvalue_reference and add_rvalue_reference, which both enforce __is_referenceable on their own?
I would really appreciate any insight into why each project chose their solutions!
Thanks! Any idea why the authors might have added const, then?
My best guess is temporary (hopefully) insanity:
https://github.com/llvm-mirror/libcxx/commit/6063ec176d5056683d6ddd310c2e3a8f1c7e1b46#diff-48f5ee43879b5ad38888f0a6ead10113R1245
;-)
I removed the const and ran the current unit tests and nothing failed.
For anything referenceable, the two implementations do the same thing, since the extraneous const in libc++ is meaningless but also harmless.
(Judging from the diff, it certainly looks like temporary insanity to me :) Seems to be a C&P issue from a (wrong) implementation of is_copy_assignable.)
For anything non-referenceable (i.e., cv void or abominable function types), libstdc++ returns false_type.
In libc++, add_{l,r}value_reference returns it unchanged (this depends on an issue resolution that postdates C++14). Sprinkling a const on top does nothing for AFTs and adds a const for the voidy types.
We then go to is_assignable, which SFINAE-tests the well-formedness of declval<T>() = declval<U>(), for either T == U == some AFT or T == some void type and U = some const-qualified void type. In all cases the expression is ill-formed (in a SFINAE-friendly manner), so we get false_type back.
The two are equivalent.
__is_referenceable is a non-standard, internal libstdc++ routine. (That doesn't mean it's bad, just that I wouldn't expect libc++ to use it). Also, the "is referenceable" concept came along much later than is_move_assignable.
The __is_referenceable helps when dealing with "abominable functions"; things like int (*) (double) &&.
Looks like I need to write more tests :-)
Using a slightly modified version of Howard Hinnants's C++11 stack allocator which is documented here and here, with std::basic_string and compiling with gcc which is using libstdc++, the following example (see it live):
const unsigned int N = 200;
arena<N> a;
short_alloc<char, N> ac(a) ;
std::basic_string<char,std::char_traits<char>,short_alloc<char, N>> empty(ac);
gives the following error(amongst others):
error: no matching function for call to 'short_alloc<char, 200ul>::short_alloc()'
if (__n == 0 && __a == _Alloc())
^
However it works without error when compiling with clang and using libc++ (see it live).
The stdlibc++ implementation of std::basic_string expects the allocator to have a default constructor.
Does C++11 require allocators to be default constructible? Which implementation is correct?
No, C++11 does not require an allocator have default constructor, if we look at the draft C++11 standard section 17.6.3.5 [allocator.requirements] it contains Table 28 Allocator requirements which does not contain a requirement for a default constructor and later on in the section a minimal conforming interface is provided:
[ Example: the following is an allocator class template supporting the
minimal interface that satisfies the requirements of Table 28:
template <class Tp>
struct SimpleAllocator {
typedef Tp value_type;
SimpleAllocator(ctor args );
template <class T> SimpleAllocator(const SimpleAllocator<T>& other);
Tp *allocate(std::size_t n);
void deallocate(Tp *p, std::size_t n);
};
โend example ]
which does not contain a default constructor.
There is a libstdc++ bug report: basic_string assumes that allocators are default-constructible which says:
The empty-string optimization of basic_string assumes that allocators
are default constructible. While this used to be the case in C++98, it
is no longer true in C++11, as now allocators are allowed to have
state.
Consider the attached example program. Compiling with
g++ -std=c++11 -c t.cpp
produces an error message, even though it should compile fine. The
problem is the the "_S_construct" calls "_Alloc()", which does not
exist.
Note that the C++11 standard does not require default constructors.
(Section 17.6.3.5, Table 28). In particular, the SimpleAllocator
example from Section 17.6.3.5 would trigger the same bug, too.
and the response was:
This is hardly the only C++11 allocator requirement missing from
std::string, ALL of the new requirements are missing, and unlikely to
be implemented until we switch to a non-COW string implementation.
This is fixed as of gcc 5.0:
Fixed for GCC 5 (when using the new string ABI)
We can confirm this using gcc 5 on wandbox