Re: C++0x: unfortunate interaction of rvalue references and concepts
On 6 dic, 15:06, SG <s.gesem...@gmail.com> wrote:
Hi!
I noticed some unfortunate gotchas when using both features (rvalue
references and concepts/templates) which I'm going to mention here.
Most importantly: The current spec draft seems to contain such a bug
in the way std::vector (and possibly other containers as well) are
defined:
in 23.2.6 (page 826 of N2798.pdf) we have
requires AllocatableElement<Alloc, T, const T&>
void push_back(const T& x);
requires AllocatableElement<Alloc, T, T &&>
void push_back(T&& x);
The problem is: rvalue references are allowed to bind to lvalues.
Overloading is necessary in this case to distinguish between lvalues
and rvalues. But not all types might be copyable which is why the
first push_back function has an such an additional requirement.
Unfortunately it won't contribute to overload resolution for non-
copyable types. In case of non-copyconstructible types it's possible
to call push_back on lvalues that are possibly destroyed because only
push_back(T&&) is the only function in the overload resolution set.
Quick fix: Add this declaration:
requires !AllocatableElement<Alloc, T, const T&>
void push_back(const T& x) = delete;
so that a push_back function taking an lvalue reference is ALWAYS part
of the overload resolution set.
If that is what happens, this is of course a bug. Using
std::HasConstructor1 (ConceptGCC's workaround for variadic
HasConstructor concept) doesn't do this, so the problem would be in
the concept specificacion of AllocatableElement.
Here is another nother gotcha:
The template type parameter deduction rule for rvalue references that
is needed for perfect forwarding makes implementing move semantics
error-prone:
template<typename T> void foo(T const& x); // #1
template<typename T> void foo(T && x); // #2
In this case #2 is ALWAYS called for non-const objects since T might
be deduced to be an lvalue reference. To circumvent this we need to
apply SFINAE a la
template<typename T>
typename disable_if_reference<T,void>::type
foo(T && x); // #2
I agree. However as unintuitive as it may look at first sight,
template<typename T> void f(T&&);
is mostly "anything goes". Unless something is done, we should avoid
it. As I understand, std::forward doesn't deduce T because of exactly
the same reason.
There's a third case where this type parameter deduction may cause
problems: Conceptualized perfect forwarding doesn't seem to be easily
possible:
concept C<typename T> {}
concept_map C<int> {}
template<C T>
void do_something(T && x);
int main() {
int k = 42;
do_something(23); // OK
do_something(k); // fails since T=int& and !C<int&>
}
So, we have to resort to code like this:
template<typename T>
requires C<typename remove_reference<T>::type>
void do_something(T && x);
the same :P
template<typename T> void f(T&&);
should be avoided. In any case, std::forward solution must be applied:
do_something<int>(23); // T not deduced
do_something<int>(k); // neither here
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]