The following code tries to emplace_back a default-initialized profile class object into a pmr vector. As vector is initialized with the allocator, it enriches the childs constructor with its own allocator argument. However, there's a second argument which is added and this is why I get a huge error:
Demo
#include <memory_resource>
#include <cstdio>
struct profile
{
using allocator_type = std::pmr::polymorphic_allocator<std::byte>;
profile(allocator_type allocator = {}) {}
};
struct update
{
using allocator_type = std::pmr::polymorphic_allocator<std::byte>;
update(allocator_type allocator = {})
: profiles_( allocator )
{
}
std::pmr::vector<profile> profiles_;
};
int main() {
update u;
u.profiles_.emplace_back();
}
For full error message check out above demo. The key part seems to be this:
stl_construct.h: In substitution of 'template<class _Tp, class ... _Args> constexpr decltype (::new(void*(0)) _Tp) std::construct_at(_Tp*, _Args&& ...) [with _Tp = profile; _Args = {profile, const std::pmr::polymorphic_allocator<profile>&}]':
You can see that an argument of type profile is added additionally to the construct_at method, which is why the constructor of profile receives two arguments instead of just one (the allocator). Why is that and how can I prevent this from happening?
If your type advertises that it supports uses-allocator construction, then all of the constructors should support it.
In your case there are only implicit move and copy constructors which do not support it. The former is used by std::vector's modifiers, e.g. by emplace_back in case a reallocation and move of elements becomes necessary. The profile you are complaining about corresponds to the argument to such a move-insertion.
So add overloads
profile(profile&&, allocator_type) noexcept;
//or
profile(std::allocator_arg_t, allocator_type, profile&&) noexcept;
and
profile(const profile&, allocator_type);
//or
profile(std::allocator_arg_t, allocator_type, const profile&) noexcept;
or equivalent overloads. (Using the std::allocator_arg_t variant is probably a safer choice, so I would recommend you use it for your default constructors as well.)
Focus on the template arguments
I can create a stack (an adapter class template from standard library) object like this,
stack<int, vector<int>> myStack;
I know the second template argument means the underlying data structure of the stack. But why the following line doesn't give a compile time error?
stack<int, vector<string>> myStack;
Notice that I'm declaring a stack to contain elements of type int, but at the same time I'm declaring the underlying data structure to hold string elements.
Functionally, It works as if it was a stack of string elements.
What you are doing is undefined behaviour. Nevertheless, I am going to explain why it seems to work fine.
The container adapter std::stack<T, TContainer> contains several type symbols that are aliases for types that will be commonly used. There is a list here.
One that concerns us here is std::stack::value_type. It basically determines what type the methods std::stack::push and friends expect:
void push( const value_type& value );
We can also see how it is defined:
using value_type = typename TContainer::value_type
So, the type that is used in all actions is actually only based on the second type, TContainer ! In your case, that is vector<string>::value_type, so value_type will be an alias to string. The type used for T, int in your case, is not used.
Thus, everything seems to work.
But even though this works in your case with your particular compiler, it is actually not allowed:
The behavior is undefined if T is not the same type as Container::value_type. (since C++17)
You can find the source for this quote here.
From the documentation:
T - The type of the stored elements. The behavior is undefined if T is not the same type as Container::value_type. (since C++17)
Undefined behaviour means it might compile, it might even work, or it might wipe your hard drive.
How it fails, if it does, is implementation dependant.
If I had to guess, I would imagine that your implementation is discarding the int template argument, and just uses Container::value_type instead.
Once we get to C++17, this will be explicitly illegal. Some compilers will already not appreciate this code..
For example:
#include <stack>
#include <string>
#include <vector>
int main() {
std::stack<int, std::vector<std::string>> s;
s.push(3);
}
Fails to compile under OS X (clang, libc++, c++11) with:
blah.cc:7:7: error: no matching member function for call to 'push'
s.push(3);
~~^~~~
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/stack:194:10: note:
candidate function not viable: no known conversion from 'int' to 'const value_type' (aka
'const std::__1::basic_string<char>') for 1st argument
void push(const value_type& __v) {c.push_back(__v);}
^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/stack:197:10: note:
candidate function not viable: no known conversion from 'int' to 'value_type' (aka 'std::__1::basic_string<char>') for
1st argument
void push(value_type&& __v) {c.push_back(_VSTD::move(__v));}
^
1 error generated.
I really don't understand this, I thought that compiler first executes what is in braces and then gives the result to the most appropriate function. Here it looks like it gives the function an initializer list to deal with it...
#include <string>
#include <vector>
using namespace std;
void func(vector<string> v) { }
void func(vector<wstring> v) { }
int main() {
func({"apple", "banana"});
}
error:
<stdin>: In function 'int main()':
<stdin>:11:27: error: call of overloaded 'func(<brace-enclosed initializer list>)' is ambiguous
<stdin>:11:27: note: candidates are:
<stdin>:6:6: note: void func(std::vector<std::basic_string<char> >)
<stdin>:8:6: note: void func(std::vector<std::basic_string<wchar_t> >)
Why isn't my func(vector<string> v) overload called, and can I make it so?
This one was subtle.
std::vector has a constructor taking two range iterators. It is a template constructor (defined in 23.6.6.2 of the C++11 Standard):
template<typename InputIterator>
vector(InputIterator first, InputIterator last,
const allocator_type& a = allocator_type());
Now the constuctor of std::vector<wstring> accepting an initializer_list is not a match for the implicit conversion in your function call, (const char* and string are different types); but the one above, which is of course included both in std::vector<string> and in std::vector<wstring>, is a potentially perfect match, because InputIterator can be deduced to be const char*. Unless some SFINAE technique is used to check whether the deduced template argument does indeed satisfy the InputIterator concept for the vector's underlying type, which is not our case, this constructor is viable.
But then again, both std::vector<string> and std::vector<wstring> have a viable constructor which realizes the conversion from the braced initializer list: hence, the ambiguity.
So the problem is in the fact that although "apple" and "banana" are not really iterators(*), they end up being seen as such. Adding one argument "joe" to the function call fixes the problem by disambiguating the call, because that forces the compiler to rule out the range-based constructors and choose the only viable conversion (initializer_list<wstring> is not viable because const char* cannot be converted to wstring).
*Actually, they are pointers to const char, so they could even be seen as constant iterators for characters, but definitely not for strings, as our template constructor is willing to think.
C++11 removed the requirement that the value type of all containers be CopyConstructible and Assignable (although specific operations on containers may impose these requirements). In theory, that should make it possible to define, for example, std::deque<const Foo>, which was not possible in C++03.
Unexpectedly, gcc 4.7.2 produced its usual vomit of incomprehensible errors [1] when I tried this, but clang at least made the error readable and clang with libc++ compiled it with no errors.
Now, when two different compilers produce different results, it always makes me wonder what the correct answer is, and so I searched out all the references I could find to const/assignable/value types/containers, etc., etc. I found almost a decade's worth of very similar questions and answers, some of them here on SO and others in the various C++ mailing lists, amongst other places, including the Gnu buganizer, all of which basically can be summarized as following dialogue.
Q: Why can't I declare std::vector<const int> (as a simplified example)
A: Why on earth would you want to do that? It's nonsensical.
Q: Well, it makes sense to me. Why can't I do it?
A: Because the Standard requires value types to be assignable.
Q: But I'm not planning on assigning them. I want them to be const after I've created them.
A: That's not the way it works. Next question!
with a mild dashing of:
A2: C++11 has decided to allow that. You'll just have to wait. In the meantime, rethink your ridiculous design.
These don't seem like very compelling answers, although possibly I'm biased because I fall into the category of "but it makes sense to me". In my case, I'd like to have a stack-like container in which things pushed onto the stack are immutable until they are popped, which doesn't strike me as a particularly odd thing to want to be able to express with a type system.
Anyway, I started thinking about the answer, "the standard requires all containers' value types to be assignable." And, as far as I can see, now that I found an old copy of a draft of the C++03 standard, that's true; it did.
On the other hand, the value type of std::map is std::pair<const Key, T> which doesn't look to me like it's assignable. Still, I tried again with std::deque<std::tuple<const Foo>>, and gcc proceeded to compile it without batting an eye. So at least I have some kind of workaround.
Then I tried printing out the value of std::is_assignable<const Foo, const Foo> and std::is_assignable<std::tuple<const Foo>, const std::tuple<const Foo>>, and it turns out that the former is reported as not assignable, as you'd expect, but the latter is reported as assignable (by both clang and gcc). Of course, it's not really assignable; attempting to compile a = b; is rejected by gcc with the complaint error: assignment of read-only location (this was just about the only error message I encountered in this quest which was actually easy to understand). However, without the attempt to do an assignment, both clang and gcc are equally happy to instantiate the deque<const>, and the code seems to run fine.
Now, if std::tuple<const int> really is assignable, then I can't complain about the inconsistency in the C++03 standard -- and, really, who cares -- but I find it disturbing that two different standard library implementations report that a type is assignable when in fact, attempting to assign to a reference of it will lead to a (very sensible) compiler error. I might at some point want to use the test in a template SFINAE, and based on what I saw today, it doesn't look very reliable.
So is there anyone who can shed some light on the question (in the title): What does Assignable really mean? And two bonus questions:
1) Did the committee really mean to allow instantiating containers with const value types, or did they have some other non-assignable case in mind?, and
2) Is there really a significant difference between the constnesses of const Foo and std::tuple<const Foo>?
[1] For the truly curious, here's the error message produced by gcc when trying to compile the declaration of std::deque<const std::string> (with a few line-endings added, and an explanation if you scroll down far enough):
In file included from /usr/include/x86_64-linux-gnu/c++/4.7/./bits/c++allocator.h:34:0,
from /usr/include/c++/4.7/bits/allocator.h:48,
from /usr/include/c++/4.7/string:43,
from /usr/include/c++/4.7/random:41,
from /usr/include/c++/4.7/bits/stl_algo.h:67,
from /usr/include/c++/4.7/algorithm:63,
from const_stack.cc:1:
/usr/include/c++/4.7/ext/new_allocator.h: In instantiation of ‘class __gnu_cxx::new_allocator<const std::basic_string<char> >’:
/usr/include/c++/4.7/bits/allocator.h:89:11: required from ‘class std::allocator<const std::basic_string<char> >’
/usr/include/c++/4.7/bits/stl_deque.h:489:61: required from ‘class std::_Deque_base<const std::basic_string<char>, std::allocator<const std::basic_string<char> > >’
/usr/include/c++/4.7/bits/stl_deque.h:728:11: required from ‘class std::deque<const std::basic_string<char> >’
const_stack.cc:112:27: required from here
/usr/include/c++/4.7/ext/new_allocator.h:83:7:
error: ‘const _Tp* __gnu_cxx::new_allocator< <template-parameter-1-1> >::address(
__gnu_cxx::new_allocator< <template-parameter-1-1> >::const_reference) const [
with _Tp = const std::basic_string<char>;
__gnu_cxx::new_allocator< <template-parameter-1-1> >::const_pointer =
const std::basic_string<char>*;
__gnu_cxx::new_allocator< <template-parameter-1-1> >::const_reference =
const std::basic_string<char>&]’ cannot be overloaded
/usr/include/c++/4.7/ext/new_allocator.h:79:7:
error: with ‘_Tp* __gnu_cxx::new_allocator< <template-parameter-1-1> >::address(
__gnu_cxx::new_allocator< <template-parameter-1-1> >::reference) const [
with _Tp = const std::basic_string<char>;
__gnu_cxx::new_allocator< <template-parameter-1-1> >::pointer = const std::basic_string<char>*;
__gnu_cxx::new_allocator< <template-parameter-1-1> >::reference = const std::basic_string<char>&]’
So what's going on here is that the standard (§ 20.6.9.1) insists that the default allocator have member functions:
pointer address(reference x) const noexcept;
const_pointer address(const_reference x) const noexcept;
but if you instantiate it with a const template argument (which is apparently UB), then reference and const_reference are the same type, and so the declarations are duplicated. (The body of the definition is identical, for what it's worth.) Consequently, no allocator-aware container can deal with an explicitly const value type. Hiding the const inside a tuple allows the allocator to instantiate. This allocator requirement from the standard was used to justify closing at least a couple of old libstdc++ bugs about problems with std::vector<const int>, although it doesn't strike me as a solid point of principle. Also libc++ works around the problem in the obvious simple way, which is to provide a specialization of allocator<const T> with the duplicate function declarations removed.
In C++03, Assignable was defined by table 64 in §23.1/4,
Expression Return type Post-condition
t = u T& t is equivalent to u
On the one hand this requirement was not met for std::map. On the other hand it was too strict a requirement for std::list. And C++11 demonstrated that it's not even necessary for std::vector, in general, but is imposed by use of certain operations (such as assignment).
In C++11 the corresponding requirement is named CopyAssignable and is defined by table 23 in §17.6.3.1/2,
Expression Return type Return value Post-condition
t = v T& t t is equivalent to v, the
value of v is unchanged
The main differences are that container elements no longer need to be CopyAssignable, and that there's a corresponding requirement MoveAssignable.
Anyway, a structure with a const data member is clearly not assignable unless one chooses to read “equivalent to” with a very peculiar interpretation.
The only operation-independent element type requirement in C++11 is, as far as I can see (from table 96 in §23.2.1/4) that it must be Destructible.
Regarding std::is_assignable, it does not quite test the CopyAssignable criterion.
Here’s what std::is_assignable<T, U> implies, according to table 49 in C++11 §20.9.4.3/3:
“The expression
declval<T>() = declval<U>() is
well-formed when treated
as an unevaluated operand
(Clause 5). Access
checking is performed as if
in a context unrelated to T
and U. Only the validity of
the immediate context of
the assignment expression
is considered. [Note: The
compilation of the
expression can result in
side effects such as the
instantiation of class
template specializations
and function template
specializations, the
generation of
implicitly-defined
functions, and so on. Such
side effects are not in the
“immediate context” and
can result in the program
being ill-formed. —end
note ]”
Essentially this implies an access/existence + argument type compatibility check of operator=, and nothing more.
However, Visual C++ 11.0 doesn't appear to do the access check, while g++ 4.7.1 chokes on it:
#include <iostream>
#include <type_traits>
#include <tuple>
using namespace std;
struct A {};
struct B { private: B& operator=( B const& ); };
template< class Type >
bool isAssignable() { return is_assignable< Type, Type >::value; }
int main()
{
wcout << boolalpha;
wcout << isAssignable< A >() << endl; // OK.
wcout << isAssignable< B >() << endl; // Uh oh.
}
Building with Visual C++ 11.0:
[D:\dev\test\so\assignable]
> cl assignable.cpp
assignable.cpp
[D:\dev\test\so\assignable]
> _
Building with g++ 4.7.1:
[D:\dev\test\so\assignable]
> g++ assignable.cpp
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits: In substitution of 'template static decltype (((declval)()=(declval)(), std::__sfinae_types::__one()))std::__is_assignable_helper::__test(int) [with _
Tp1 = _Tp1; _Up1 = _Up1; _Tp = B; _Up = B] [with _Tp1 = B; _Up1 = B]':
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1055:68: required from 'constexpr const bool std::__is_assignable_helper::value'
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1060:12: required from 'struct std::is_assignable'
assignable.cpp:10:59: required from 'bool isAssignable() [with Type = B]'
assignable.cpp:16:32: required from here
assignable.cpp:7:24: error: 'B& B::operator=(const B&)' is private
In file included from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/move.h:57:0,
from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/stl_pair.h:61,
from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/stl_algobase.h:65,
from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/char_traits.h:41,
from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ios:41,
from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ostream:40,
from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/iostream:40,
from assignable.cpp:1:
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1049:2: error: within this context
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits: In substitution of 'template static decltype (((declval)()=(declval)(), std::__sfinae_types::__one())) std::__is_assignable_helper::__test(int) [with _
Tp1 = _Tp1; _Up1 = _Up1; _Tp = B; _Up = B] [with _Tp1 = B; _Up1 = B]':
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1055:68: required from 'constexpr const bool std::__is_assignable_helper::value'
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1060:12: required from 'struct std::is_assignable'
assignable.cpp:10:59: required from 'bool isAssignable() [with Type = B]'
assignable.cpp:16:32: required from here
assignable.cpp:7:24: error: 'B& B::operator=(const B&)' is private
In file included from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/move.h:57:0,
from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/stl_pair.h:61,
from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/stl_algobase.h:65,
from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/char_traits.h:41,
from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ios:41,
from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ostream:40,
from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/iostream:40,
from assignable.cpp:1:
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1049:2: error: within this context
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits: In instantiation of 'constexpr const bool std::__is_assignable_helper::value':
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1060:12: required from 'struct std::is_assignable'
assignable.cpp:10:59: required from 'bool isAssignable() [with Type = B]'
assignable.cpp:16:32: required from here
assignable.cpp:7:24: error: 'B& B::operator=(const B&)' is private
In file included from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/move.h:57:0,
from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/stl_pair.h:61,
from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/stl_algobase.h:65,
from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/bits/char_traits.h:41,
from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ios:41,
from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/ostream:40,
from d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/iostream:40,
from assignable.cpp:1:
d:\bin\mingw\bin\../lib/gcc/i686-pc-mingw32/4.7.1/../../../../include/c++/4.7.1/type_traits:1055:68: error: within this context
[D:\dev\test\so\assignable]
> _
So, summing up, the standard's std::is_assignable appears to be of very limited utility, and as of this writing it can't be relied on for portable code.
EDIT: Replaced <utility> with correct <type_traits. Interestingly it didn't matter for g++. Not even for the error message, so I just let that be as it was.
I'm giving this to Alf but I wanted to add a couple of notes for future reference.
As Alf says, std::is_*_assignable really only check for the existence (explicit or implicit) of an appropriate assignment operator. They don't necessarily check to see whether it will be well-formed if instantiated. This works fine for the default assignment operators. If there is a member declared const, the default assignment operators will be deleted. If a base class has a deleted assignment operator, the default assignment operator will be deleted. So if you just let the defaults do their thing, it should be fine.
However, if you do declare operator=, it becomes your responsibility (if you care) to ensure that it is appropriately deleted. For example, this will compile and run (at least with clang), and reports that C is_assignable:
#include <iostream>
#include <type_traits>
#include <tuple>
using namespace std;
struct A { const int x; A() : x() {}};
struct C {
struct A a;
C& operator=( C const& other);
};
template< class Type >
bool isAssignable() { return is_assignable< Type&, const Type& >::value; }
int main()
{
wcout << boolalpha;
wcout << isAssignable< A >() << endl; // false
wcout << isAssignable< C >() << endl; // true
C c1;
C c2;
}
The absence of the assignment operator's definition isn't noted until link-time, and in this case not at all because the assignment operator is never used. But note that a use of C::operator= contingent on std::is_assignable would be allowed to compile. Of course, I couldn't have defined C::operator= in a way that resulted in assigning to its member a, because that member isn't assignable.
That's not a particularly interesting example though. What get's interesting is the use of templates, such as the std::tuple issue which started this whole question. Let's add a couple of templates into the above, and actually define C::operator= through assignment to its member a:
using namespace std;
template<bool> struct A {
A() : x() {}
const int x;
};
template<bool B> struct C {
struct A<B> a;
C& operator=( C const& other) {
this->a = other.a;
return *this;
}
};
template< class Type >
bool isAssignable() { return is_assignable< Type&, const Type& >::value; }
int main()
{
wcout << boolalpha;
wcout << isAssignable< A<false> >() << endl; // false
wcout << isAssignable< C<false> >() << endl; // true
C<false> c1;
C<false> c2;
c1 = c2; // Bang
return 0;
}
Without the assignment at the end, the code compiles and runs (under clang 3.3) and reports that A<false> is not assignable (correct) but that C<false> is assignable (surprise!). The actual attempt to use C::operator= reveals the truth, because it is not until that point that the compiler attempts to actually instantiate that operator. Up to then, and through the instantiations of is_assignable, the operator was just a declaration of a public interface, which is -- as Alf says -- all that std::is_assignable is really looking for.
Phew.
So bottom line, I think this is a deficiency in both the standard and the standard library implementations with respect to standard aggregate objects whose operator= should be deleted if any of the component types is not assignable. For std::tuple, § 20.4.2.2 lists as requirements for operator= that all component types be assignable, and there ae similar requirements for other types, but I don't think that requirement requires library implementors to delete inapplicable operator=.
But, then, as far as I can see, nothing stops library implementations from doing the deletion (except the annoyance factor of conditionally deleting assignment operators). In my opinion after obsessing about this for a couple of days, they should do so, and furthermore the standard should require them to do so. Otherwise, reliable use of is_assignable impossible.
Consider the following code to iterate over an intrusive list using the BOOST_FOREACH macro:
#include <boost/foreach.hpp>
#include <boost/intrusive/list.hpp>
typedef boost::intrusive::list<
boost::intrusive::list_base_hook<> > MyList;
void iterate (const MyList& xs) {
BOOST_FOREACH (MyList::const_reference node, xs);
}
int main () {
MyList xs;
iterate (xs);
return 0;
}
Given boost version 1.48 the code fails with clang 3.2 (SVN) and gcc 4.6.3, but works with gcc 4.5.3. With non-const-qualified parameter xs to iterate the code works. With C++11 enabled all of the compilers accept the code. When using boost-1.46 both gcc versions accept the code, but clang still doesn't.
Is the code at hand a misuse of the BOOST_FOREACH macro, or is the error at boosts side? Is there a workaround that is nicer than iteration with regular for-loop?
Edit:
I pasted the error messages to pastebin (both are very verbose) for GCC and clang.
Here is what I could gather from the logs as well as my deductions as to the cause of failure.
Short version: for some reason BOOST_FOREACH attempts to copy the data which is not possible.
There is a note on the Extensibility page:
Making BOOST_FOREACH Work with Non-Copyable Sequence Types
For sequence types that are non-copyable, we will need to tell BOOST_FOREACH to not try to make copies. If our type inherits from boost::noncopyable, no further action is required. If not, we must specialize the boost::foreach::is_noncopyable<> template [...] Another way to achieve the same effect is to override the global boost_foreach_is_noncopyable() function. Doing it this way has the advantage of being portable to older compilers.
From the diagnosis, it is unclear whether the type is properly configured, so you might want to give it a go.
Pruned diagnosis and analysis.
/usr/include/boost/foreach.hpp:571:37: error: no matching constructor for initialization of 'boost::intrusive::list< >'
::new(this->data.address()) T(t);
^ ~
/usr/include/boost/foreach.hpp:648:51: note: in instantiation of member function 'boost::foreach_detail_::simple_variant<boost::intrusive::list< > >::simple_variant' requested here
return auto_any<simple_variant<T> >(*rvalue ? simple_variant<T>(t) : simple_variant<T>(&t));
^
/usr/include/boost/intrusive/list.hpp:1490:35: note: candidate constructor not viable: 1st argument ('const boost::intrusive::list< >') would lose const qualifier
BOOST_MOVABLE_BUT_NOT_COPYABLE(list)
^
/usr/include/boost/move/move.hpp:371:7: note: expanded from macro 'BOOST_MOVABLE_BUT_NOT_COPYABLE'
TYPE(TYPE &);\
/usr/include/boost/intrusive/list.hpp:1497:4: note: candidate constructor not viable: no known conversion from 'const boost::intrusive::list< >' to 'const value_traits' (aka 'const boost::intrusive::detail::base_hook_traits<boost::intrusive::list_base_hook< >, boost::intrusive::list_node_traits<void *>, 1, boost::intrusive::default_tag, 1>') for 1st argument;
list(const value_traits &v_traits = value_traits())
^
/usr/include/boost/intrusive/list.hpp:1506:4: note: candidate constructor not viable: no known conversion from 'const boost::intrusive::list< >' to '::boost::rv<list< >> &' for 1st argument;
list(BOOST_RV_REF(list) x)
^
/usr/include/boost/intrusive/list.hpp:1502:4: note: candidate constructor template not viable: requires at least 2 arguments, but 1 was provided
list(Iterator b, Iterator e, const value_traits &v_traits = value_traits())
^
I tried to isolate the error as much as possible (removing backtraces etc..) Apparently the problem stems from boost::intrusive::list, and more precisely the inability to build a new boost::intrusive::list<> from a boost::intrusive::list<> const.
The most promising constructor is defined by a macro:
BOOST_MOVABLE_BUT_NOT_COPYABLE(list)
which expands to
list(list&);
which is the way boost emulates move semantics for non-copyable types in C++03. However it cannot move from a const item since the const qualifier would be lost.
This looks to be part of the trickery used by BOOST_FOREACH to avoid multiple evaluation of the container argument (in case it is a function invocation) though I am a little surprised it tries to copy the argument here.
Since you are using gcc > 4.6 and clang 3.2, you could use C++11's range-based for looops:
#include <boost/foreach.hpp>
#include <boost/intrusive/list.hpp>
typedef boost::intrusive::list<
boost::intrusive::list_base_hook<> > MyList;
void iterate (const MyList& xs) {
for(const auto &node : xs) {
// do something with node
}
}
int main () {
MyList xs;
iterate (xs);
return 0;
}
You could also use std::for_each:
void iterate (const MyList& xs) {
std::for_each(xs.begin(), xs.end(),
[](MyList::const_reference node) {
// do something with node
}
);
}