Perfect forwarding and ambiguity of function-parameter binding - c++

I am experimenting with the perfect forwarding feature of C++11. Gnu g++ compiler reports an ambiguity issue of function-parameter binding (the error is shown after the source code below). My question is why is it so, as following the function-parameter binding process I don't see the ambiguity. My reasoning is as follows: call to tf(a) in main() binds to tf(int&) since a is an lvalue. Then function tf forwards the lvalue reference int& a to function g hence the function void g(int &a) should be uniquely invoked. Thus I do not see the reason for ambiguity. The error disappears when the overloaded function g(int a) is removed from the code. This is strange as g(int a) cannot be a candidate for binding with int &a.
Here is my code:
void g(int &&a)
{
a+=30;
}
void g(int &a)
{
a+=10;
}
void g(int a) //existence of this function originates the ambiguity issue
{
a+=20;
}
template<typename T>
void tf(T&& a)
{
g(forward<T>(a));;
}
int main()
{
int a=5;
tf(a);
cout<<a<<endl;
}
Compilation g++ -std=c++11 perfectForwarding.cpp reports the following errors:
perfectForwarding.cpp: In instantiation of ‘void tf(T&&) [with T = int&]’:
perfectForwarding.cpp:35:7: required from here
perfectForwarding.cpp:24:3: error: call of overloaded ‘g(int&)’ is ambiguous
perfectForwarding.cpp:24:3: note: candidates are:
perfectForwarding.cpp:6:6: note: void g(int&&) <near match>
perfectForwarding.cpp:6:6: note: no known conversion for argument 1 from ‘int’ to ‘int&&’
perfectForwarding.cpp:11:6: note: void g(int&)
perfectForwarding.cpp:16:6: note: void g(int)

This is strange as g(int a) cannot be a candidate for binding with int &a.
That's not true. If you remove the g(int&) overload then g(int) will get called. When both are declared it is ambiguous, because both are viable candidates and require no conversions.

Adding on top of Jonathan Wakely's answer.
First of all, the issue has nothing to do with perfect forwarding and we can remove tf from the picture.
For the time being consider just this code:
void g(int) {}
int main() {
int a = 5; // a is an lvalue
g(a); // ok
g(std::move(a)); // std::move(a) casts a to an rvalue and this call is also ok
}
This illustrates that a function that takes a parameter by value can take both lvalues and rvalues.
Now suppose we add
void g(int &) {}
then the first call, g(a);, becomes ambigous because g(int &) can take non-const lvalues and nothing else. The second call, g(std::move(a)) is still ok and still calls g(int) because g(int &) can't take rvalues.
Now replace g(int &) with g(int &&). The latter function can take non-const rvalues only. Hence the call g(a) is ok and calls g(int). However, g(std::move(a)) is now ambiguous.
At this point it becomes obvious that if we have the three overloads together, then the two calls become ambiguous. Actually, there's no reason for having the three overloads. Depending on the type T, most often we have either
g(T) or
g(T&) or
g(const T&) or
g(const T&) and g(T&&).

Related

cannot bind non-const lvalue reference of type ‘T&’ to an rvalue of type ‘T’ t++ which std::atomic<T>

this is my code
#include <iostream>
#include <atomic>
using namespace std;
class T{
public:
int i = 0;
friend T operator++( T& t, int);
};
T operator++( T& t, int){
t.i++;
return T(); // please ignore this. I only care for it to compile right now
}
int main() {
atomic<T> t;
t++;
return 0;
}
I am trying to use atomic with a custom class B but im getting error:
*Compilation error #stdin compilation error #stdout 0s 4400KB
prog.cpp: In function ‘int main()’:
prog.cpp:21:3: error: cannot bind non-const lvalue reference of type ‘T&’ to an rvalue of type ‘T’
t++;
^~
In file included from prog.cpp:2:
/usr/include/c++/8/atomic:202:7: note: after user-defined conversion: ‘std::atomic<_Tp>::operator _Tp() const [with _Tp = T]’
operator _Tp() const noexcept
^~~~~~~~
prog.cpp:11:4: note: initializing argument 1 of ‘T operator++(T&, int)’
T operator++( T& t, int){
^~~~~~~~*
I am using friend to avoid using explicit conversion
(T(t))++;
if I am defining the operator++ with const like this:
friend T operator++(const T& t, int);
it compiles but then of course its useless to me.
When you do t++, it is the same as (t.operator T())++, which is equivalent to (t.load(std::memory_order_seq_cst))++.
This returns a copy of the value held by the atomic, which is an rvalue. Incrementing an rvalue doesn't make sense (It is destroyed immediately), so perhaps you want to lock, load and then store?
The std::atomic<T>::operator++ operators are only defined for integers and pointers (see the fine green print here).
The compiler attempts to invoke std::atomic<T>::operator T to obtain a temporary copy of the contained T instance, and then call your own operator ++ on it, which therefore requires a const reference parameter. atomic provides no way of locking, calling your own operator, and unlocking. Since this could lead to deadlocks (if your operator acquires some other lock), this would subvert atomic's purpose anyways.
You probably need to use a lock like std::mutex explicitly.

C++14 Template function forwarding reference deduced explicitly by pointer

Explain, please, what's wrong with this code?
An unusual behaviour is that f3 is working, when deduced implicitly, but is not, when is forcely specialized.
template<typename T>
void f1(T x) {}
template<typename T>
void f2(T& x) {}
template<typename T>
void f3(T&& x) {}
int main()
{
int x = 0;
f1(x); // ok
f2(x); // ok
f3(x); // ok
f1<int>(x); // ok
f2<int>(x); // ok
f3<int>(x); // error
}
VS2017. Compilier message:
error C2664: "void f3(T &&)": unable to convert argument 1 from "int" to "T &&"
I thought I'm doing exactly the same specifying int explicitly. And only difference that deduction in this case f3(x); was done by compiler and in this one f3<int>(x); by me.
I'm not 100% sure, please someone correct me if I'm wrong:
Because in case of f3(x) the deduced type is f3<int&>( int& && ).
There is an Errror becuase l-values and l-values references cannot be used to initialize r-value references. R-values references can only be initialized with r-values only.
You can use PRETTY_FUNCTION to get the Function being called along with the arguments that are deduced.
f1(a); // ok deduces to void f1(T) [T = A *]
f2(a); // ok deduces to void f2(T &) [T = A *]
f3(a); // ok deduces to void f3(T &&) [T = A *&]
f1<A*>(a); // ok . No Deduction as T already specified void f1(T) [T = A *]
f2<A*>(a); // ok . No Deduction as T already specified to void f2(T) [T = A *]
f3<A*>(a); // Errror becuase l-values and l-values references
// cannot be used to r-value references.
// R-values references can only be initialized with r-values only.

Having a function only accept non-const lvalues

I have a function which sorts two vectors with the first of them as ordering criterion. Its signature is
template<typename A, typename B>
void sort(A&& X, B&& Y)
{
..
}
The problem is that universal references would allow nonsense cases like
sort(vector<int>{ 2,1,3 }, vector<int>{ 3,1,2 });
where an rvalue will be destroyed afterwards (nonsense).
Asking explicitly for a lvalue doesn't work since
template<typename A, typename B>
void sort(A& X, B& Y) ... // (*)
sort(vector<int>{2,1,3}, vector<int>{3,1,2});
for some reason the above compiles (I thought only const lvalues were allowed to bind to rvalues and to prolong their lifetime?).
If I add const to the lvalue reference then the function will no longer be able to modify the vectors and sort them.
My questions are:
1) Why in the example marked with // (*) can I bind a rvalue to a lvalue that is not even const ? Why instead something like int& r = 20; isn't allowed? What's the difference?
2) How can I solve my issue i.e. having the function accept only lvalues and not rvalue temporaries? (If it's possible, of course)
Obviously I'm allowed to use any C++ version available
The answer is: your compiler is wrong.
Check on gcc or clang or similar and you'll get something like this:
prog.cpp: In function 'int main()': prog.cpp:9:45: error: invalid
initialization of non-const reference of type 'std::vector&' from
an rvalue of type 'std::vector' sort(vector{2,1,3},
vector{3,1,2});
^ prog.cpp:6:6: note: initializing argument 1 of 'void sort(A&, B&) [with A =
std::vector; B = std::vector]' void sort(A& X, B& Y) { }
You can use the /Za compiler option to turn this into an error:
error C2664: 'void sort<std::vector<int,std::allocator<_Ty>>,std::vector<_Ty,std::allocator<_Ty>>>(A &,B &)' : cannot convert argument 1
from 'std::vector<int,std::allocator<_Ty>>' to 'std::vector<int,std::allocator<_Ty>> &'
with
[
_Ty=int
, A=std::vector<int,std::allocator<int>>
, B=std::vector<int,std::allocator<int>>
]
and
[
_Ty=int
]
and
[
_Ty=int
]
A non-const reference may only be bound to an lvalue
Note that /Za has had quite some issues in the past and even nowadays still breaks <windows.h>, so you cannot use it for all compilation units anyway. In a 2012 posting titled "MSVC /Za considered harmful", Microsoft senior engineer Stephan T. Lavavej even recommends not using the flag, but you should also have a look at the comments at STL Fixes In VS 2015, Part 2, where he says:
We've definitely had meetings about the /Za and /Zc conformance
options. We ultimately want to get to a point where VC is conformant
by default, without having to request extra options, so that becomes
the most-used and most-tested path. As you can see in the post, I've
been working towards this in the STL by removing non-Standard
machinery whenever possible.
So, chances are this will be a compilation error by default in some future version of MSVC.
One other thing: The C++ standard does not distinguish between errors and warnings, it only talks about "diagnostic messages". Which means that MSVC actually is conforming as soon it produces a warning.
As noted by other answers, the compiler is wrong.
Without having to change compiler of compiler options:
struct sfinae_helper {};
template<bool b>
using sfinae = typename std::enable_if<b, sfinae_helper>::type*;
// sfinae_helper, because standard is dumb: void*s are illegal here
template<class A, class B,
sfinae<!std::is_const<A>::value&&!std::is_const<B>::value> = nullptr
>
void sort(A& X, B& Y) ... // (*)
sort(vector<int>{2,1,3}, vector<int>{3,1,2});
will fail to compile in MSVC2013 as well, and should be compliant in compliant compilers.
Note that while deducing A and B as const X is not legal under the standard, explicitly passing const X as A or B is.
A final approach is:
template<typename A, typename B>
void sort(A& X, B& Y) ... // (*)
template<typename A, typename B>
void sort(A&& X, B&& Y) = delete;
where we generate an explicitly deleted one that should be preferred to the A&, B& one. I do not know if MSVC properly picks the perfectly forwarded one in that case, but I hope so.
As an answer to the X problem you're trying to solve rather than the Y problem you asked... the right answer is that you shouldn't do what you're trying to do. Being unable to imagine how something can be useful is not an adequate reason to go out of your way to prevent people from being able to do it.
And, in fact, I don't even have to suggest this in the abstract: here are two concrete examples where accepting a temporary object would be useful.
You might only care about one of the two objects:
interesting_container A;
// fill A
sort(an_ordering_criterion(), A);
The containers aren't 'self-contained'; e.g. a container that provides a view into another one:
vector<int> A, B;
// fill A and B
sort(make_subsequence(A, 1, 10), make_subsequence(B, 5, 14));
You can explicitly delete undesired overloadings of sort function:
#include <iostream>
#include <vector>
#include <cstdlib>
template< typename X, typename Y >
void
sort(X &, Y &)
{
static_assert(!std::is_const< X >{});
static_assert(!std::is_const< Y >{});
}
template< typename X, typename Y >
int
sort(X const &, Y &) = delete;
template< typename X, typename Y >
int
sort(X &, Y const &) = delete;
template< typename X, typename Y >
int
sort(X const &, Y const &) = delete;
int
main()
{
std::vector< int > v{1, 3, 5};
std::vector< int > const c{2, 4, 6};
::sort(v, v); // valid
{ // has been explicitly deleted
//::sort(v, c);
//::sort(c, v);
//::sort(c, c);
}
{ // not viable: expects an l-value for 1st argument
//::sort(std::move(v), v);
//::sort(std::move(v), c);
//::sort(std::move(c), v);
//::sort(std::move(c), c);
}
{ // not viable: expects an l-value for 2nd argument
//::sort(v, std::move(v));
//::sort(v, std::move(c));
//::sort(c, std::move(v));
//::sort(c, std::move(c));
}
{ // not viable: expects an l-value for 1st or 2nd argument
//::sort(std::move(v), std::move(v));
//::sort(std::move(v), std::move(c));
//::sort(std::move(c), std::move(v));
//::sort(std::move(c), std::move(c));
}
return EXIT_SUCCESS;
}

Compiler error when passing rvalue reference through variadic templates

There is a requirement where I need to pass an rvalue from 1 function to another function via variadic template. To avoid real code complexity, minimal example is below using int:
void Third (int&& a)
{}
template<typename... Args>
void Second (Args&&... args) {
Third(args...);
}
void First (int&& a) {
Second(std::move(a)); // error: cannot bind ‘int’ lvalue to ‘int&&’
Third(std::move(a)); // OK
}
int main () {
First(0);
}
First(0) is called properly. If I invoke Third(int&&) directly then it works fine using std::move(). But calling Second(Args&&...) results in:
error: cannot bind ‘int’ lvalue to ‘int&&’
Third(args...); ^
note: initializing argument 1 of ‘void Third(int&&)’
void Third (int&& a)
What is the correct way to achieve the successful compilation for Second(Args&&...)?
FYI: In real code the Second(Args&&...) is mix of lvalues, rvalues and rvalue references. Hence if I use:
Third(std::move(args...));
it works. But when there are mix of arguments, it has problems.
You have to use std::forward:
template<typename... intrgs>
void Second (intrgs&&... args) {
Third(std::forward<intrgs>(args)...);
}
To preserve the rvalue-ness, you have to move or forward the parameter(s)
template<typename... intrgs>
void Second (intrgs&&... args) {
Third(std::forward<intrgs>(args)...);
}

g++ 4.9.2 regression on pass reference to 'this'

This is a minimized part of Pointer to implementation code:
template<typename T>
class PImpl {
private:
T* m;
public:
template<typename A1>
PImpl(A1& a1) : m(new T(a1)) {
}
};
struct A{
struct AImpl;
PImpl<AImpl> me;
A();
};
struct A::AImpl{
const A* ppub;
AImpl(const A* ppub)
:ppub(ppub){}
};
A::A():me(this){}
A a;
int main (int, char**){
return 0;
}
It compilable on G++4.8 and prior and works as well. But G++4.9.2 compiler raise following errors:
prog.cpp: In constructor 'A::A()':
prog.cpp:24:15: error: no matching function for call to 'PImpl<A::AImpl>::PImpl(A*)'
A::A():me(this){}
^
prog.cpp:24:15: note: candidates are:
prog.cpp:9:5: note: PImpl<T>::PImpl(A1&) [with A1 = A*; T = A::AImpl]
> PImpl(A1& a1) : m(new T(a1)) {
^
prog.cpp:9:5: note: no known conversion for argument 1 from 'A*' to 'A*&'
prog.cpp:2:7: note: PImpl<A::AImpl>::PImpl(const PImpl<A::AImpl>&)
class PImpl {
^
prog.cpp:2:7: note: no known conversion for argument 1 from 'A*' to 'const PImpl<A::AImpl>&'
But it can be fixed by small hack. If i pass '&*this' instead of 'this' then it bring to compilable state.
Is it G++ regression or new C++ standards feature which eliminate backward compatibility?
We can make a simpler example that compiles on neither g++ 4.9 nor clang:
template <typename T>
void call(T& ) { }
struct A {
void foo() { call(this); }
};
int main()
{
A().foo();
}
That is because this is, from the standard, [class.this] (§9.3.2):
In the body of a non-static (9.3) member function, the keyword this is a prvalue expression whose value
is the address of the object for which the function is called.
You cannot take an lvalue reference to a prvalue, hence the error - which gcc explains better than clang in this case:
error: invalid initialization of non-const reference of type A*& from an rvalue of type A*
If we rewrite call to either take a const T& or a T&&, both compilers accept the code.
This didn't compile for me with gcc-4.6, so it seems that gcc-4.8 is where the regression occurred. It seems what you want is to take A1 by universal reference, ie: PImpl(A1 && a1). This compiles for me with both gcc-4.6, gcc-4.8 and gcc-4.9.