#include <type_traits>
#include <iostream>
template<typename T>
struct Wrapper {
T value;
operator T&() & { std::cout << "call const ref" << std::endl; return this->value; }
operator const T&() const& { std::cout << "call const ref" << std::endl; return this->value; }
operator T&&() && { std::cout << "call move" << std::endl; return std::move(this->value); }
operator const T&() const&& = delete;
operator T&&() & = delete;
operator T&&() const& = delete;
};
class A {
public:
A& operator=(const A&) { std::cout << "use copy" << std::endl; return *this; }
A& operator=(A&&) { std::cout << "use move" << std::endl; return *this; }
};
int main() {
Wrapper<A> b;
A bb;
bb = std::move(b);
}
I compiled this code with gcc10.2, and get the following error
test.cc: In function ‘int main()’:
test.cc:24:21: error: ambiguous overload for ‘operator=’ (operand types are ‘A’ and ‘std::remove_reference<Wrapper<A>&>::type’ {aka ‘Wrapper<A>’})
24 | bb = std::move(b);
| ^
test.cc:17:12: note: candidate: ‘A& A::operator=(const A&)’
17 | A& operator=(const A&) { std::cout << "use copy" << std::endl; return *this; }
| ^~~~~~~~
test.cc:18:12: note: candidate: ‘A& A::operator=(A&&)’
18 | A& operator=(A&&) { std::cout << "use move" << std::endl; return *this; }
| ^~~~~~~~
But I tried the same code with clang in cppinsights.io, and compiled successfully.
link
So, what cause the difference between gcc and clang?
And how do I change the Wrapper to fix it?
https://godbolt.org/z/37dPqafGK
Quoted text is from the C++20 standard, but links are to N4861.
First let's minimize the given code. The results with GCC 11.2 and Clang 12.0.1 are as indicated in the question (Clang accepts, GCC rejects) if three of the conversion functions are deleted, leaving only:
operator const T&() const&; // not deleted
operator T&&() &&; // not deleted
operator const T&() const&& = delete;
From this we can infer that Clang only accepts the code because, in trying to form an implicit conversion sequence that would enable A& A::operator=(const A&) to be called, it selects the deleted Wrapper<A>::operator const T&() const&& to perform the conversion to const A&. Thus, forming the implicit conversion sequence fails, and A& A::operator=(const A&) is not viable, leaving only the other candidate.
The divergence between GCC and Clang appears to be related to CWG issue 2525. The issue is as follows: it appears that the intent of Note 1 to [over.ics.best.general]/2 ([over.ics.best]/2 in N4861) is that if an implicit conversion sequence involves a deleted function, the implicit conversion sequence is still considered to be formed, and only if the candidate that requires this implicit conversion sequence is selected, then the program is ill-formed since it requires a call to the deleted function. But the normative wording does not seem to reflect this intention.
GCC and Clang agree that the declaration const A& t = std::move(b); is ill-formed because it invokes the deleted function Wrapper<A>::operator const A&() const&&. Thus, Clang appears to be following the normative wording as it currently exists: since the initialization const A& t = std::move(b) would be ill-formed, it follows that std::move(b) is not implicitly convertible to const A& under [conv]/3, and thus the implicit conversion sequence required to make the copy assignment operator a viable candidate does not exist, and only the move assignment operator is viable according to Clang. But GCC is following what the note seems to be telling us: to ignore (at the overload resolution stage) the fact that one of the implicit conversion sequences contains a deleted function. This ultimately results in GCC being unable to decide whether to call the copy assignment operator or the move assignment operator.
It looks like the problem is that you have two overloads. There's the overload resolution of A::operator=, but there is also the overload resolution of the Wrapper<A> conversion operator in the conversion sequence. C++ cannot resolve multiple overloads simultaneously.
For a simpler case, imagine if A::operator= was overloaded for int and float, and Wrapper<A> defined both operator int and operator float.
This is of course only the case if there is an overload, i.e. there are multiple candidate conversion operators in Wrapper<A>. But I don't see a reason why clang should exclude 2 out of the 3 conversion operators.
I also don't seen an obvious fix. The problem itself looks ambiguous to me.
Related
In the following MCVE, std::assignable_from reports that A<double>& can't be assigned to from A<double> -- when it clearly can.
#include <iostream>
template <typename T>
class A
{
public:
const A& operator= (const A& other) { return *this; }
};
int main ()
{
A<double> d1, d2; d1 = d2; //this works fine
std::cout << std::boolalpha
<< "Is double assignable? " << std::assignable_from<double&, double> << '\n' //Says true, as it should
<< "Is A<double> assignable? " << std::assignable_from<A<double>&, A<double>> << '\n'; //Says false
return 0;
}
I know why. assignable_from expects operator= to have a return type of A&, not const A&.
template <class _LTy, class _RTy>
concept assignable_from = is_lvalue_reference_v<_LTy>
&& common_reference_with<const remove_reference_t<_LTy>&, const remove_reference_t<_RTy>&>
&& requires(_LTy _Left, _RTy&& _Right) {
{ _Left = static_cast<_RTy&&>(_Right) } -> same_as<_LTy>;
};
Is there an alternative, short of writing my own concept for assignability? I've always had = return const &, because I thought it was dumb to say (A=B)=C.
I've always had = return const &, because I thought it was dumb to say (A=B)=C.
You are free to do that, but there are consequences when you break with established conventions. You have encountered one of them. std::assignable_from requires that you follow established C++ conventions for assignment operators. And this includes returning a modifiable reference to the assigned type in your operator= overload.
It should also be noted that if you return a const& from the assignment operator, you cannot =default it. C++ is very serious about returning modifiable references from assignment operators. It's an expected part of the language.
And writing your own concept for assignment won't help when you have to pass your type to a conceptualized function/class that is constrained on std::assignable_from or any constraint that uses it.
So just follow C++'s conventions.
There is indeed a problem with assignment operator and specifically the implicitly defined one, and also the increment and decrement operator as they are implemented, for historical reasons, in the standard library. It looks like the community has forgotten this old debate. You are right to look for a limitation of the assignment operator "reachability", but the way you do it is not the way it used to be advised.
First let's consider the problem:
#include <vector>
std::vector <int> f();
void g(std::vector <int> v) {
decltype (auto) v1 = f() = v; //(1)
decltype (auto) it = ++v.begin(); //(2)
v1. size() //undefined behavior;
++it; //undefined behavior;
}
The problem here is that the assignment operator and the preincrement operator are returning a reference to a temporary materialization. But the lifetime of those temporaries end at the end of their respective full-expressions (1) and (2).
The way to fix the problem is to ref qualify those operators.
#include <concepts>
struct A {
A& operator = (const A&) &
=default;
A& operator = (A&&) &
=default;
//unfortunately, it implies verbosity:
A(const A&) =default;
A(A&&) = default;
A() = default;
A& operator ++ () &;
};
static_assert (std::assignable_from <A&, A>);
static_assert (!std::assignable_from <A, A>);
It compiles with /permissive but fails with /permissive-. What is not conforming and how to fix it?
Why it's fine in (2) but fails in (4)(3)?
If I remove operator long it also fine.
How to fix it without changing call site (3,4)?
#include <string>
struct my
{
std::string myVal;
my(std::string val): myVal(val) {}
operator std::string() { return myVal; };
operator long() { return std::stol(myVal); };
};
int main()
{
struct MyStruct
{
long n = my("1223"); // (1)
std::string s = my("ascas"); // (2)
} str;
str.s = my("ascas"); // (3)
str.n = my("1223"); // (4)
}
error message
error C2593: 'operator =' is ambiguous
xstring(2667): note: could be 'std::basic_string<...> &std::basic_string<...>::operator =(const _Elem)'
with
[
_Elem=char
]
xstring(2648): note: or 'std::basic_string<...> &std::basic_string<...>::operator =(const std::basic_string<...> &)'
xstring(2453): note: or 'std::basic_string<...> &std::basic_string<...>::operator =(std::basic_string<...> &&) noexcept(<expr>)'
Source1.cpp(17): note: while trying to match the argument list '(std::string, my)'
I suppose you meant it's fine in (2) but fails in (3)
Note that the #2 is initialization, which calls the constructor of std::string; the #3 is assignment, which calls the assignment operator of std::string. They're different things.
The invocation of assigment operator is ambiguous because the assignment operator of std::string has an overload taking char, which could be implicitly converted from long (which is a stardard conversion), then leads to ambiguity (with the assignment operators taking std::string, as the compiler complained). Both the implicit conversion sequence contain one user-defined conversion (from my to std::string or long), they have the same rank in onverload resolution.
The invocation of constructor is fine because it doesn't have such overload (taking char).
The problem is that in the case #2 there is used a constructor while in the case #3 there is used an assignment operator.
The assignment operator is overloaded like
basic_string& operator=(charT c);
But there is no constructor that accepts only one argument of the type charT
So for the case #2 there is used the user-defined conversion operator
operator std::string() { return myVal; };
and then the constructor
basic_string(basic_string&& str) noexcept;
In the case #3 there are two posiibilities.
The first one is to call the conversion operator
operator std::string() { return myVal; };
and then the assignment operator
basic_string& operator=(basic_string&& str)
And the second one is to call the conversion operator
operator long() { return std::stol(myVal); };
and then the assignment operator
basic_string& operator=(charT c);
It is interesting to note the following additional case.
If you will wrote
str.s = { my("ascas") };
then there will not be an ambiguity. The compiler will select the operator that accepts an std::initializer_list. That is it will select the assignment operator
basic_string& operator=(initializer_list<charT>);
In this case there will be used the conversion operator
operator long() { return std::stol(myVal); };
but as the string "ascas" can not be converted to the type long a runtime error will occur
terminate called after throwing an instance of 'std::invalid_argument'
what(): stol
Vlad from Moscow answer have a nice explanation. But there was no solution.
Here it is using SFINAE
template<typename T = long, typename = std::enable_if_t<
std::is_same_v<T, long> || std::is_same_v<T, int>>>
operator T() const
{
return l();
}
operator std::string() const
{
return s();
}
Is there a way to disable conversion operators? Marking them "= delete" messes up other things.
Consider the following code:
class Foo
{
public:
Foo() :mValue(0) {}
~Foo() = default;
Foo(int64_t v) { mValue = v; }
Foo(const Foo& src) = default;
bool operator==(const Foo& rhs) { return mValue == rhs.mValue; }
/* after commenting these lines the code will compile */
operator int() const = delete;
operator int64_t() const = delete;
private:
int64_t mValue;
};
int main()
{
Foo foo1(5);
Foo foo2(10);
bool b1 = (foo1 == foo2);
bool b2 = (foo1 == 5);
}
This won't compile because gcc complains that the == operator is ambiguous:
test.cc: In function ‘int main()’:
test.cc:25:21: error: ambiguous overload for ‘operator==’ (operand types are ‘Foo’ and ‘int’)
bool b2 = (foo1 == 5);
^
test.cc:25:21: note: candidates are:
test.cc:25:21: note: operator==(int, int) <built-in>
test.cc:25:21: note: operator==(int64_t {aka long int}, int) <built-in>
test.cc:10:10: note: bool Foo::operator==(const Foo&)
bool operator==(const Foo& rhs) { return mValue == rhs.mValue; }
^
However, after commenting the conversion operators, the code will compile and run nicely.
The first question is: why do the deleted conversion operators create an ambiguity for the == operator? I thought they should disable implicit Foo -> int conversions but they seem to affect int -> Foo conversions which does not sound logic to me.
Second one: is there a way to mark the conversion operators deleted? Yes, by not declaring them - but I'm looking for a way that anyone in the future will see that those conversions are disabled by design.
Any use of a deleted function is ill-formed (the program will not
compile).
If the function is overloaded, overload resolution takes place first,
and the program is only ill-formed if the deleted function was
selected.
In you case programm can't select conversion becouse you have 3 variant
int -> Foo
Foo -> int
Foo -> int64
Second question:
you can leave it as it is, but always use explicit conversion for int
bool b2 = (foo1 == Foo(5));
Here's what I think is the crux of the matter:
[dcl.fct.def.delete]:
A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed.
...
A deleted function is implicitly an inline function ([dcl.inline]).
[class.member.lookup/4]:
If C contains a declaration of the name f, the declaration set
contains every declaration of f declared in C that satisfies the
requirements of the language construct in which the lookup occurs.
Even if you delete the function, you still declare it. And a declared function will participate in overload resolution. It's only when it's the resolved overload, that the compiler checks if it's deleted.
In your case, there is an obvious ambiguity when those function declarations are present.
Is it possible for assignment operator to be deduced as a special case of member function template?
For example, I have a class template with one bool parameter and want to implement assign operation regardless any particular value of the template argument.
#include <iostream>
template<bool sw>
struct A {
A() {
std::cout << __PRETTY_FUNCTION__ << '\n';
}
template<bool input_sw>
A & operator = (const A<input_sw> &a) {
std::cout << __PRETTY_FUNCTION__ << '\n';
return *this;
}
};
int main()
{
A<true> a;
A<true> b;
a = b;
}
In the code snippet above clang and gcc-compiled binaries print out nothing about assignment -- as far as I can tell default assignment is generated here despite possibility to deduce it from the template.
It is correct for them to not print anything out. What is happening is that there is an implicit copy assignment operator created for A<true> with this signature:
A<true>& operator=(const A<true>& );
Thus, when we do overload resolution, there are two viable candidates:
A<true>& operator=(const A<true>& ); // implicit copy assignment
A<true>& operator=(const A<input_sw>& ); // [ with input_sw = true ]
Both operators take the same argument (const A<true>&), so they are equivalently good candidates from this perspective. But non template functions are preferred to function template specializations, which makes the implicit copy assignment operator the best viable candidate.
Now, consider an alternative. What if you declared your operator template this way:
template<bool input_sw>
A & operator =(A<input_sw> &a) { ... }
That is, not const. This isn't good practice, and I'm presenting this solely for illustrative purposes. In this case, our two candidates for a=b are:
A<true>& operator=(const A<true>& ); // implicit copy assignment
A<true>& operator=(A<input_sw>& ); // [ with input_sw = true ]
Now the two candidates do not take the same argument. Our operator template takes a reference to non-const. In the case of two references, the one referring to the last cv-qualified type is preferred, which in this case would be the operator. Drop that const, and now your function is preferred, and you will see something printed.
C++ is the best.
I don't understand why the following code doesn't compile:
#include <iostream>
class Test {
public:
Test() {
std::cout << "Constructor" << std::endl;
}
Test(const Test&) {
std::cout << "Copy Constructor" << std::endl;
}
Test& operator=(const Test&) {
std::cout << "Assign Op" << std::endl;
return *this;
}
Test& operator=(const volatile Test&) {
std::cout << "Volatile Assign Op" << std::endl;
return *this;
}
};
volatile Test func() {
Test a;
return a;
}
int main() {
Test b;
volatile Test c;
b = c; // this line is correct
b = func(); // this line doesnt compile correct
return 0;
}
the line:
b = c; // this line is correct
While:
b = func(); // this line doesn t compile
The compile complains about:
test.cc: In function ‘int main()’:
test.cc:31:14: error: no match for ‘operator=’ in ‘b = func()()’
test.cc:31:14: note: candidates are:
test.cc:12:11: note: Test& Test::operator=(const Test&)
test.cc:12:11: note: no known conversion for argument 1 from ‘volatile Test’ to ‘const Test&’
test.cc:17:11: note: Test& Test::operator=(const volatile Test&)
test.cc:17:11: note: no known conversion for argument 1 from ‘volatile Test’ to ‘const volatile Test&’
In the beginning I thought that it was due to constructor elision, which I was experimenting how to disable it using volatile, when I got into this situation. Compiling with:
-fno-elide-constructors
Didn't make any difference.
Any explanation for this?
Testing with:
g++ (GCC) 4.6.3 20120306 (Red Hat 4.6.3-2)
Per [dcl.init.ref]/5, for a reference (i.e. the parameter of Test::operator=()) to be initialized by binding to an rvalue, the reference must be a const non-volatile lvalue reference, or an rvalue reference:
— Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference shall be an rvalue reference.
The line b = c; works because you are binding a reference (the parameter of Test::operator=()) to an lvalue.