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.
Related
I have some trouble getting started with C++20 concepts. I want to define a concept that requires a class to have a member called count_ that must be of type int:
#include <concepts>
template <typename T>
concept HasCount = requires(T thing) {
{ thing.count_ } -> std::same_as<int>;
};
The following struct should satisfy this concept:
struct BaseTableChunk {
BaseTableChunk* next_;
int count_ = 0;
int data_[1000];
};
Then, the following code does not compile:
template <HasCount Chunk>
class BaseTable {
void doSomething();
};
int main() {
BaseTable<BaseTableChunk> table{};
return 0;
}
The compiler gives the following error:
note: constraints not satisfied
In file included from /usr/include/c++/10/compare:39,
from /usr/include/c++/10/bits/stl_pair.h:65,
from /usr/include/c++/10/bits/stl_algobase.h:64,
from /usr/include/c++/10/bits/char_traits.h:39,
from /usr/include/c++/10/ios:40,
from /usr/include/c++/10/ostream:38,
from /usr/include/c++/10/iostream:39,
from Minimal2.cxx:1:
/usr/include/c++/10/concepts:57:15: required for the satisfaction of ‘__same_as<_Tp, _Up>’ [with _Tp = int&; _Up = int]
/usr/include/c++/10/concepts:62:13: required for the satisfaction of ‘same_as<int&, int>’
/usr/include/c++/10/concepts:57:32: note: the expression ‘is_same_v<_Tp, _Up> [with _Tp = int&; _Up = int]’ evaluated to ‘false’
57 | concept __same_as = std::is_same_v<_Tp, _Up>;
As I understand it, thing.count_ is evaluated to return an int& instead of an int, which is not what I'd expect.
Should I instead test for { thing.count_ } -> std::same_as<int&>? (Which then does compile.) That seems rather counter-intuitive to me.
If count_ is a member of thing with declared type int, then the expression thing.count_ is also of type int and the expression's value category is lvalue.
A compound requirement of the form { E } -> C will test whether decltype((E)) satisfies C. In other words, it tests whether the type of the expression E, not the type of the entity that E might name, satisfies the concept.
The type of the expression as obtained by decltype((E)) translates the value category to reference-qualification of the type. Prvalues result in non-references, lvalues in lvalue references and xvalues in rvalue references.
So, in your example, the type will be int& but the concept std::same_as requires strict match of the type, including its reference-qualification, making it fail.
A simple solution would be to just test against int&:
{ thing.count_ } -> std::same_as<int&>;
A similar solution as mentioned also in the question comments is since C++23:
{ auto(thing.count_) } -> std::same_as<int>
auto is deduced to int and the functional-style cast expression int(...) is always a prvalue, so that it will never result in a reference-qualified type.
Another alternative is to write a concept to replace std::same_as that doesn't check exact type equality but applies std::remove_reference_t or std::remove_cvref_t to the type first, depending on how you want to handle const-mismatch. Notably, the first solution will not accept a const int member or const-qualified T with int member, while the second one will (because auto never deduces a const).
However, you should be careful here if you intend to check that thing has a member of type int exactly. All of the solutions above will also be satisfied if it has a thing member of type reference-to-int.
Excluding the reference case cannot be done with a compound requirement easily, but a nested requirement may be used instead:
template <typename T>
concept HasCount = requires(T thing) {
requires std::same_as<decltype(thing.count_), int>;
};
The nested requirement (introduced by another requires) checks not only validity of the expression but also whether it evaluates to true. The difference in the check here is that I use decltype(thing.count_) instead of decltype((thing.count_)). decltype has an exception when it names a member directly through a member access expression (unparenthesized). In that case it will produce the type of the named entity, not of the expression. This verifies that count_ is a int, not a int&.
There are also further edge cases you should consider if T is const-qualified or a reference type. You should carefully consider under which conditions exactly the concept should be satisfied in these cases. Depending on the answer the suggested solutions may or may not be sufficient.
And as another edge case you need to consider whether you really want to accept any member or only non-static ones. All of the solutions above assume that you will accept a static member as well.
Also, you need to consider whether a member inherited from a base class should be accepted. All of the suggested solutions above do accept them.
All of the above solutions also assume that the member should be accepted only if it is public. The check in the concept is done from a context not related to any class, so accessibility based on context doesn't work anyway, and there probably is no reason to accept a private member that then wouldn't actually be usable in the function that is constrained by the concept.
#include <unordered_map>
#include <type_traits>
int main()
{
std::unordered_multimap<int, string> m{ { 1, "hello" } };
auto b = std::is_move_assignable_v<decltype(*m.begin())>;
// b is true
auto v = *m4.begin(); // ok
v = std::move(*m4.begin()); // compile-time error
}
Issue:
If b is true, then v = *m4.begin(); should be ok.
Question:
Why does std::is_move_assignable not behave as expected?
Error messages: (Clang 3.8 + Visual Studio 2015 Update 3)
error : cannot assign to non-static data member 'first' with
const-qualified type 'const int'
first = _Right.first;
~~~~~ ^
main.cpp(119,5) : note: in instantiation of member function
'std::pair<const int, std::basic_string<char, std::char_traits<char>,
std::allocator<char> > >::operator=' requested here
v = *m4.begin(); // error
If b is true, then v = *m4.begin(); should be ok.
v = *m4.begin(); is copy assignment, not move assignment. Move assignment is v = std::move(*m4.begin());.
But as T.C. points out and Yam Marcovic answered as well, your use of std::is_move_assignable is wrong, because decltype(*m.begin()) is a reference type. You end up not quite checking for move assignability, and your check does end up right for v = *m4.begin();.
For move assignment, the check should be std::is_move_assignable_v<std::remove_reference_t<decltype(*m.begin())>>.
Anyway, is_move_assignable doesn't try too hard to check whether the type is move assignable, it only checks whether the type reports itself as move assignable.
Given
template <typename T>
struct S {
S &operator=(S &&) { T() = "Hello"; return *this; }
};
std::is_move_assignable<S<int>>::value will report true, because the signature S<int> S::operator=(S<int> &&); is well-formed. Attempting to actually call it will trigger an error, since T() cannot be assigned to, and even if it could be, it couldn't be assigned a string.
In your case, the type is std::pair<const int, std::string>. Note the const there.
std::pair<T1, T2> currently always declares operator=, but if either T1 or T2 cannot be assigned to, attempting to actually use it will produce an error.
Class templates may make sure that a valid signature of operator= is only declared if its instantiation would be well-formed to avoid this problem, and as also pointed out by T.C., std::pair will do so in the future.
First, as *begin() returns an lvalue reference, you're actually passing an lvalue reference to std::is_move_assignable, which is then implemented by means of std::is_assignable<T&, T&&>. Now, notice the forwarding reference there, which converts to an lvalue reference. This means that by asking std::is_move_assignable<SomeType&> you have effectively asked std::is_assignable<T&, T&>—i.e. copy-assignable. Granted, this is a bit misleading.
You might want to test this code to see my point:
#include <iostream>
#include <type_traits>
struct S {
S& operator=(const S&) = default;
S& operator=(S&&) = delete;
};
int main()
{
std::cout << std::is_move_assignable<S>::value; // 0
std::cout << std::is_move_assignable<S&>::value; // 1
}
All that said, *begin() in this case returns a pair<const int, string>&, so it would not be any-kind-of-assignable anyhow, because you cannot assign to a const int. However, as #hvd pointed out, since the function is declared in principal in the pair template, the type trait does not understand that if it were ever generated into an actual function by the compiler, the latter would encounter an error.
Dereferencing .begin() iterator of empty container is undefined behaviour.
I'm trying to implement a priority queue which uses an object which has a const member that is used to define the priority of objects in the queue. The following is a stripped down version of what I'm using
#include <vector>
#include <queue>
class Event {
public:
Event(float _time) : time(_time) {};
const float time;
};
struct EventComp {
public:
bool operator()(const Event& a, const Event& b) const {
return a.time < b.time;
}
};
class EventQueue {
private:
std::priority_queue<Event, std::vector<Event>, EventComp> events;
};
int main(int argc, char *argv[])
{
EventQueue q;
}
When I try to compile (using g++ -std=c++11), I get the following error messages, which I don't quite understand.
g++ -std=c++11 events.cpp -o events
In file included from /usr/include/c++/4.8/queue:62:0,
from events.cpp:2:
/usr/include/c++/4.8/bits/stl_heap.h: In instantiation of ‘void std::__adjust_heap(_RandomAccessIterator, _Distance, _Distance, _Tp, _Compare) [with _RandomAccessIterator = __gnu_cxx::__normal_iterator<Event*, std::vector<Event> >; _Distance = long int; _Tp = Event; _Compare = EventComp]’:
/usr/include/c++/4.8/bits/stl_heap.h:448:15: required from ‘void std::make_heap(_RandomAccessIterator, _RandomAccessIterator, _Compare) [with _RandomAccessIterator = __gnu_cxx::__normal_iterator<Event*, std::vector<Event> >; _Compare = EventComp]’
/usr/include/c++/4.8/bits/stl_queue.h:411:48: required from ‘std::priority_queue<_Tp, _Sequence, _Compare>::priority_queue(const _Compare&, _Sequence&&) [with _Tp = Event; _Sequence = std::vector<Event>; _Compare = EventComp]’
events.cpp:15:7: required from here
/usr/include/c++/4.8/bits/stl_heap.h:315:29: error: use of deleted function ‘Event& Event::operator=(Event&&)’
*(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first + __secondChild));
^
events.cpp:4:7: note: ‘Event& Event::operator=(Event&&)’ is implicitly deleted because the default definition would be ill-formed:
class Event {
^
events.cpp:4:7: error: non-static const member ‘const float Event::time’, can’t use default assignment operator
In file included from /usr/include/c++/4.8/queue:62:0,
from events.cpp:2:
/usr/include/c++/4.8/bits/stl_heap.h:321:29: error: use of deleted function ‘Event& Event::operator=(Event&&)’
*(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first
^
I assume that some part of the internal structure of the priority_queue needs the move assignment operator to insert elements, but the const member seems to prevent that operator from being defined. I tried using the rule of five, but it didn't seem to work out. What do I need to add to the class definitions to get this working?
edit: I'm aware that I can remove the const qualifier from the member variable, make it private, and add an accessor member function to get the value of the variable, but I would rather have it public and const, so I'm interested in solutions which keep the member variable as it is in the example (if that is even possible).
The default constructor of std::priority_queue<Event, ...> requires Event to be MoveAssignable because it calls std::make_heap (which has this requirement). Event is not MoveAssignable because of the const float time data member. If you remove the const qualifier from this data member, your code should compile.
I'm aware that I can remove the const qualifier from the member
variable, make it private, and add an accessor member function to
get the value of the variable, but I would rather have it public and
const, so I'm interested in solutions which keep the member variable
as it is in the example (if that is even possible).
Ultimately, to use this std::lib facility, Event will have to be MoveAssignable. This requirement can be met with either a move assignment operator, or a copy assignment operator. Either or both of these can be supplied by you, or the compiler. But when you have a const data member, the compiler supplied special member will be implicitly deleted.
Go ahead and supply your own copy and/or move assignment operator if you desire:
class Event {
public:
Event(float _time) : time(_time) {};
const float time;
Event(Event&&) = default;
Event& operator=(Event&&); // Supply this
};
I do not have a good guess as to what you might want the move (or copy) assignment operator to do, since you have stated that once constructed, you do not want time to ever change. That is a design decision which you must work out (and has more to do with software design in general, than C++ syntax).
<aside>
In the comments of the question POW notes that the code in the question compiles with clang (more accurately, with libc++). The reason for this is a nearly conforming extension in libc++:
The standard specifies this priority_queue constructor:
explicit priority_queue(const Compare& x = Compare(),
Container&& c = Container());
which calls std::make_heap with the container c. But the standard also gives leeway to implementors to replace member function signatures with default values, with equivalent overloads. libc++ replaces this single signature with the following three:
priority_queue();
explicit priority_queue(const Compare& x);
explicit priority_queue(const Compare& x, Container&& c);
And only the 3rd needs to call std::make_heap. Thus the default constructor in libc++ places fewer requirements on the value_type.
I said nearly conforming. The part that is not completely conforming is that the default constructor is not declared explicit. This was a conscious design decision on the part of the implementor to make priority_queue easier to use.
</aside>
I have a class I've written, meant to represent vectors (in the linear algebra sense). I just started writing it so it isn't finished, but i've pasted it and some test code here
I'm not really sure what's going on. I was going to write an overloaded operator<< for testing in a second, so I went ahead and put it in my main function (so I could use compiler errors to make sure I'd written it properly).
What does this warning mean? why it is looking at the address of v? I tried removing the parentheses from v, and I end up with this, a bunch of horrible template errors:
`
In function 'typename boost::range_const_iterator<C>::type boost::range_detail::boost_range_begin(const C&) [with C = vect<float, 3u>]':
/usr/local/include/boost/range/begin.hpp:164: instantiated from 'typename boost::range_const_iterator<C>::type boost::begin(const T&) [with T = vect<float, 3u>]'
prelude/more_stdlib_ostreaming.hpp:64: instantiated from 'void more_stdlib_ostreaming_detail::print_range(std::basic_ostream<_CharT, _Traits>&, const Range&) [with C = char, Tr = std::char_traits<char>, Range = vect<float, 3u>]'
prelude/more_stdlib_ostreaming.hpp:76: instantiated from 'typename more_stdlib_ostreaming_detail::snd<typename R::iterator, std::basic_ostream<_CharT, _Traits>&>::type operator<<(std::basic_ostream<_CharT, _Traits>&, const R&) [with C = char, Tr = std::char_traits<char>, R = vect3f]'
t.cpp:42: instantiated from here
Line 45: error: 'const class vect<float, 3u>' has no member named 'begin'`
I can sort of see whats going on here, tho. It looks like it is somehow using boost's range for and trying to iterate over my container, and it is failing because I haven't defined begin() and end(). The same thing happens if I instantiate v using v(some_float) rather than without the parens.
So, two questions:
Why is v() behaving differently than v? I thought that declaring a object without parens always calls the default ctor anyway, and explicitly calling the default ctor made no difference?
What is codepad's compiler (gcc 4.1.2) doing here? Does it have a template that automatically tries to generate an appropriate operator<
Also, feel free to tell me anything else I'm doing stupid/wrong/bad style here (besides rolling my own matrix math library for fun, which I know is unnecessary. I'm doing it as an exercise)
First of all, vect3f v(); declares a function (named v), taking no parameters and returning vect3f. And the operator<< that is being called is called for this function pointer (which is implicitly converted to bool, because there's no overload for function pointers).
vect3f v; is the correct way of creating default-constructed object.
And no, compiler won't try to generate ostream& operator<<(ostream& /* ... */) for you. There are however many overloads for all fundamental types and even some other types (such as std::string).
You can check all basic overloads here.
I was experimenting with tuples and encountered a problem with creating tuples.
The code example is as follows.
//a.cpp
#include <tuple>
using namespace std;
int main() {
auto te = make_tuple(); //this line is ok
auto tte = make_tuple(te); //this line gives an error.
return 0;
}
I compiled it with both g++ 4.5 (g++ -std=c++0x a.cpp) and MS VC++2010.
Both compilers are giving me an error on the second line in main().
My question is this:
Since 'te' is a well-defined variable, why can't another tuple be created with te being the content. Is this semantic correct?
I guess this is kind of a boundary case, but if the arithmetic is correct, zero should be allowed, IMHO.
FYI, the error message from gcc is:
$ gcc -std=c++0x a.cpp
In file included from a.cpp:1:0:
c:\mingw\bin\../lib/gcc/mingw32/4.5.2/include/c++/tuple: In constructor
'std::tuple<_Elements>::tuple(std::tuple<_UElements ...>&) [with _UElements = {},
_Elements = {std::tuple<>}]':
c:\mingw\bin\../lib/gcc/mingw32/4.5.2/include/c++/tuple:551:62: instantiated from
'std::tuple<typename std::__decay_and_strip<_Elements>::__type ...>
std::make_tuple(_Elements&& ...) [with _Elements = {std::tuple<>&}, typename
std::__decay_and_strip<_Elements>::__type = <type error>]'
a.cpp:6:27: instantiated from here
c:\mingw\bin\../lib/gcc/mingw32/4.5.2/include/c++/tuple:259:70: error: invalid
static_cast from type 'std::tuple<>' to type 'const std::_Tuple_impl<0u>&'
This looks like the compiler has matched your std::tuple<> against the following constructor of std::tuple<std::tuple<>> (See 20.4.2p15-17 in N3242):
template <class... UTypes> tuple(const tuple<UTypes...>& u);
Requires:
sizeof...(Types) == sizeof...(UTypes).
is_constructible<Ti , const Ui &>::value is true for all i.
Effects:
Constructs each element of *this with
the corresponding element of u.
Remark: This constructor shall not
participate in overload resolution
unless const Ui & is implicitly
convertible to Ti for all i.
I think this is a bug in the implementation of std::tuple from your compiler; the "remark" implies that this constructor should not be considered, since it won't compile.