g++ 4.6.3 and 4.7.2 fail to compile the following code (in c++0x mode) if BREAK
is defined.
template<class T> struct Test {
Test(T&) {}
#ifdef BREAK
Test(T&&) = delete;
#endif
};
void func(Test<int> const&) {}
void func(Test<double> const&) {}
int main()
{
int x = 0;
func(x);
return 0;
}
The error is
error: call of overloaded 'func(int&)' is ambiguous
while clang 3.2 RC2 and VC11 (if I replace Test(T&&) = delete; with private: Test(T&&);) accept the code.
I can't see where that should be ambiguous.
Is this a g++ issue? (I don't know what to search for in the gcc bug list...)
Deleted constructors participate in overload resolution (Are the special member functions always declared?); this is necessary so that one can use deleted constructors to prevent conversions (excerpted from 8.4.3p3):
struct onlydouble {
onlydouble(std::intmax_t) = delete;
onlydouble(double);
};
Enforcement of function deletion comes very late in the compilation process, after overload resolution (8.4.3p2) and so overload resolution cannot distinguish between constructors on the basis of deletion. gcc is correct and clang and VC11 are incorrect.
Note that the ambiguity is in the function call expression func(x), where the argument x is an lvalue of type int and the id func denotes an overload set with parameter types in the first (only) parameter of const Test<int> & and const Test<double> &; the available conversion sequences then are:
int lvalue; int &; Test<int> temporary; const Test<int> &,
int lvalue; int rvalue; double rvalue; double &&; Test<double> temporary; const Test<double> &.
The two sequences are user-defined conversion sequences of equal rank, and so are ambiguous. The fact that the constructor Test<double>::Test(double &&) is deleted is irrelevant at this stage.
There is open bug in GCC: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=54425.
CLANG is correct, GCC needs to fix this.
Related
Consider the following program:
struct A {
A(int){}
A(A const &){}
};
int main() {
A y(5);
}
The variable y is direct-initialized with the expression 5.The overload resolution selects the constructor A::A(int), which is what I expect and want, but why does it happen?
It may be so for two reasons:
either the overload A::A(int) is a better match then A::A(A const &), or the second one is not a viable overload at all.
Question: In the above program, is the constructor A::A(A const &) a viable overload for initialization of y?
Yes, the rules for constructor overloading are same as for ordinary functions. The compiler is allowed to make one user-defined conversion per parameter - as pointed by Ben Voigt - in order to match the parameters with arguments. In this case it can do int->A through A(5)
This situation is the same as:
void foo(const std::string&);
void bar(const std::string&);//1
void bar(const char*);//2
//...
foo("Hello");//Is allowed
bar("Hello");//Calls 2 as it matches exactly without a need for conversion.
So, the answer, yes it's viable overload but it's not chosen because according to overloading rules the A(int) constructor is a better match.
[class.conv.ctor]/1:
A constructor that is not explicit ([dcl.fct.spec]) specifies a
conversion from the types of its parameters (if any) to the type of
its class. Such a constructor is called a converting constructor.
[ Example:
struct X {
X(int);
X(const char*, int =0);
X(int, int);
};
void f(X arg)
{
X a = 1; // a = X(1)
X b = "Jessie"; // b = X("Jessie",0)
a = 2; // a = X(2)
f(3); // f(X(3))
f({1, 2}); // f(X(1,2))
}
— end example ]
and
[over.match.copy]/1:
Assuming that “cv1 T” is the type of the object being initialized, with T a class type, the candidate functions are selected as follows:
(1.1) The converting constructors of T are candidate functions.
Consider the following simple move-only class:
struct bar {
constexpr bar() = default;
bar(bar const&) = delete;
bar(bar&&) = default;
bar& operator=(bar const&) = delete;
bar& operator=(bar&&) = default;
};
Now, let's create a wrapper:
template <class T>
struct box {
constexpr box(T&& x)
: _payload{std::move(x)}
{}
constexpr explicit operator T() &&
{
return std::move(_payload);
}
private:
T _payload;
};
And a test:
int main()
{
auto x = box<bar>{bar{}};
auto y = static_cast<bar&&>(std::move(x));
}
Clang-6.0 is happy with this code if compiled with -std=c++14 or above. GCC-8.1 however produces the following error:
<source>: In function 'int main()':
<source>:29:45: error: invalid static_cast from type 'std::remove_reference<box<bar>&>::type' {aka 'box<bar>'} to type 'bar&&'
auto y = static_cast<bar&&>(std::move(x));
Here's a link to compiler explorer to try it out.
So my question is, who's right and who's wrong?
Simplified example:
struct bar
{
bar() = default;
bar(bar const&) = delete;
bar(bar&&) = default;
};
struct box
{
explicit operator bar() && { return bar{}; }
};
int main() { static_cast<bar&&>(box{}); }
live on godbolt.org
First of all, let's see what it means for a conversion operator to be explicit:
A conversion function may be explicit, in which case it is only considered as a user-defined conversion for direct-initialization. Otherwise, user-defined conversions are not restricted to use in assignments and initializations.
Is static_cast considered direct-initialization?
The initialization that occurs in the forms
T x(a);
T x{a};
as well as in new expressions, static_cast expressions, functional notation type conversions, mem-initializers, and the braced-init-list form of a condition is called direct-initialization.
Since static_cast is direct-initalization, it doesn't matter whether the conversion operator is marked explicit or not. However, removing explicit makes the code compile on both g++ and clang++: live example on godbolt.org.
This makes me believe that it's a g++ bug, as explicit makes a difference here... when it shouldn't.
program:
#include <stdio.h>
struct bar_t {
int value;
template<typename T>
bar_t (const T& t) : value { t } {}
// edit: You can uncomment these if your compiler supports
// guaranteed copy elision (c++17). Either way, it
// doesn't affect the output.
// bar_t () = delete;
// bar_t (bar_t&&) = delete;
// bar_t (const bar_t&) = delete;
// bar_t& operator = (bar_t&&) = delete;
// bar_t& operator = (const bar_t&) = delete;
};
struct foo_t {
operator int () const { return 1; }
operator bar_t () const { return 2; }
};
int main ()
{
foo_t foo {};
bar_t a { foo };
bar_t b = static_cast<bar_t>(foo);
printf("%d,%d\n", a.value, b.value);
}
output for gcc 7/8:
2,2
output for clang 4/5 (also for gcc 6.3)
1,1
It seems that the following is happening when creating the instances of bar_t:
For gcc, it calls foo_t::operator bar_t then constructs bar_t with T = int.
For clang, it constructs bar_t with T = foo_t then calls foo_t::operator int
Which compiler is correct here? (or maybe they are both correct if this is some form of undefined behaviour)
I believe clang's result is correct.
In both bar_t a { foo } direct-list-initialization and in a static_cast between user defined types, constructors of the destination type are considered before user defined conversion operators on the source type (C++14 [dcl.init.list]/3 [expr.static.cast]/4). If overload resolution finds a suitable constructor then it's used.
When doing overload resolution bar_t::bar_t<foo_t>(const foo_t&) is viable and will be a better match than one to any instantiation of this template resulting in the use of the cast operators on foo. It will also be better than any default declared constructors since they take something other than foo_t, so bar_t::bar_t<foo_t> is used.
The code as currently written depends on C++17 guaranteed copy elision; If you compile without C++17's guaranteed copy elision (e.g. -std=c++14) then clang does reject this code due to the copy-initialization in bar_t b = static_cast<bar_t>(foo);.
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.
class Myclass{
private:
int i;
public:
Myclass(){}
Myclass(const Myclass &lvalue){} //<---
template<typename T>Myclass(T& lvalue):i(lvalue){}
};
int main()
{
Myclass a;
Myclass b(a);
return 0;
}
The code above fails to compile with:
error: cannot convert ‘Myclass’ to ‘int’ in initialization
Is this a bug? I have tested it using g++ 5.3 and clang3.9
Nope, not a bug. And this has nothing to do with SFINAE.
Let's do overload resolution on:
Myclass b(a);
We have two viable overloads:
Myclass(Myclass const&); // your copy ctor
Myclass(Myclass& ); // your ctor template, with [T=Myclass]
Both are exact matches. One of the tiebreakers in picking the best viable candidate is to select the least cv-qualified reference - which in this case is the template. This ends up trying to initialize your int with a Myclass, hence the error. (Note, there is a tiebreaker to prefer non-templates to templates - but it's a lower ranked tiebreaker than the cv-qualification on the reference).
In this case, the solution would be to introduce SFINAE to disable this constructor in the case that it should use the copy ctor. That is:
template <class T, class = std::enable_if_t<!std::is_convertible<T*, Myclass const*>::value>>
Myclass(T& );
And now this constructor won't be viable for Myclass b(a).
The other answers are good, but I thought I'd add standard references to complement. The latest draft, section Ranking implicit conversion sequences, states:
Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if
S1 and S2 are reference bindings ([dcl.init.ref]), and the types to which the references refer are the same type except for top-level
cv-qualifiers, and the type to which the reference initialized by S2
refers is more cv-qualified than the type to which the reference
initialized by S1 refers. [ Example:
int f(const int &);
int f(int &);
int g(const int &);
int g(int);
int i;
int j = f(i); // calls f(int &)