I wondering which overload method resolution rule was applied here.
My purpose was create a new temporary instance using copy constructor,
and then pass that object to the method, so r-value reference passing.
And there are overloaded methods that accept l-value, r-value, so I expected r-value overloaded method will be invoked, but It was not.
class Kdy {
public:
Kdy() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
Kdy(Kdy&&) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
Kdy(const Kdy&) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
void DoAction(const Kdy&) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
void DoAction(Kdy&&) {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
}; // Kdy
int main() {
Kdy kdy1;
Kdy kdy2;
// DoAction(const Kdy&), Why??
// kdy1.DoAction(Kdy(kdy2))
kdy1.DoAction({kdy2});
// Then why this works?
// After copy-ctor, DoAction(Kdy&&) was invoked.
kdy1.DoAction({ {kdy2} });
// Then why this dosen't compile?
// Since { {kdy2} } becomes Kdy&&
// { { {kdy2} } } should be Kdy(Kdy&&)
// kdy1.DoAction({ { {kdy2} } });
return 0;
}
I have read a overload reference document multiple times,
but it half clear to me
https://en.cppreference.com/w/cpp/language/overload_resolution
It seems after collecting a set of candidate methods,
compiler make a decision which method best matches by their matching priority.
So obviously if there are some methods accept std::initializer_list<Kdy> as parameter then those methods are selected. (I have tested it)
Still confusing then
if exact signature matching was failed,
which resolution overload rule was applied in this context?
What made compile think match {kdy2} best suit for const Kdy& than for Kdy&&?
Also, why { { { kdy2 } } } couldn't be interpreted as Kdy(Kdy&&)?
Please shed light on this poor guy.
Thank you!
Let's take the following part of the standard as reference:
[dcl.init.list]
(3.7) Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution.
(3.9) Otherwise, if the initializer list has a single element of type E and either T is not a reference type or its referenced type is reference-related to E, the object or reference is initialized from that element ...
Section (3.9) explain why DoAction({kdy2}) select the overload DoAction(const Kdy&). The single element of the initializer list is an lvalue of type Kdy and from the two overloads of DoAction only one can bind to an lvalue; the one selected.
In DoAction({ {kdy2} }) the initializer don't have a single element of type Kdy, (3.9) is not used and a prvalue is introduced for {{kdy2}}. By (3.7) the constructors of Kdy are considered. Candidates are Kdy(Kdy&&) and Kdy(Kdy const&).
In order to select the best one, {kdy} is tried to be converted to the parameters of the ctors and again applying (3.9) the selected constructor is the copy ctor. The prvalue is then bound to the parameter of DoAction and from these overload DoAction(Kdy&&) is a better match.
For DoAction({ { {kdy2} } }) attemps are made as in the second case but when trying to convert {{kdy2}} to the parameters of the constructors it fails because the initializer list don't have a single element of type Kdy and (3.7) don't apply.
Related
#include <iostream>
#include <coroutine>
class eager {
public:
struct promise_type {
promise_type() { std::cout << "promise_type ctor" << std::endl; }
~promise_type() { std::cout << "~promise_type dtor" << std::endl; }
struct return_object {
return_object() { std::cout << "return_object ctor" << std::endl; }
~return_object() { std::cout << "~return_object dtor" << std::endl; }
operator eager() { return {}; }
};
auto get_return_object() noexcept { return return_object{}; }
constexpr auto initial_suspend() const noexcept { return std::suspend_never{}; }
constexpr auto final_suspend() const noexcept { return std::suspend_never{}; }
constexpr auto return_void() const noexcept {}
auto unhandled_exception() -> void { throw; }
};
};
auto coroutine() -> eager {
co_return;
}
auto main() -> int
{
coroutine();
return 0;
}
You can see the result for MSVC, clang and GCC here: https://godbolt.org/z/Yan9s9TPE
According to lots of articles about coroutine, the coroutine() will be transformed into...
auto coroutine() -> eager {
eager::promise_type promise;
auto res = promise.get_return_object();
// initial suspend
promise.return_void();
// final suspend
return res;
}
At a glance, since the promise object is constructed first, I thought it would be the last object to be destructed.
However, MSVC and GCC show reverse order:
// from MSVC/GCC
promise_type ctor
return_object ctor
~promise_type dtor
~return_object dtor
On the other hand, clang shows what I expected:
// from clang
promise_type ctor
return_object ctor
~return_object dtor
~promise_type dtor
Which one is right?
Or, is the destruction order of promise object and return object just unspecified by standard?
According to lots of articles about coroutine
Then "lots of articles about coroutine[sic]" are incorrect.
The result object is not on the coroutine stack. It cannot be on the coroutine stack, because it's the result object for the initial call to the coroutine.
All that the C++ standard says about get_result_object is this:
The expression promise.get_return_object() is used to initialize the glvalue result or prvalue result object of a call to a coroutine.
The call to get_return_object is sequenced before the call to initial_suspend and is invoked at most once.
It happens before initial_suspend and it gets called once. That's all it has to say. Therefore, everything else about result objects from functions work as normal; in this case, it simply gets initialized before the function properly starts instead of when the function is about to return.
By C++'s normal rules, a function's result object is on the caller's stack, not the stack of the function being called. So when promise.get_result_object() is evaluated, it is initializing storage provided by the caller.
main discards the result of the expression coroutine(). This means that it will manifest a temporary from the prvalue result object, and the type of this temporary will be eager. The temporary will be destroyed, but only after control returns to main.
Here's the tricky part: the return prvalue from get_result_object() is not eager. It's eager::promise::result_object. Initializing the return value requires performing an implicit conversion from result_object to eager. This requires manifesting a temporary of type result_object to perform that conversion on.
The standard rule for destroying temporaries is:
Temporary objects are destroyed as the last step in evaluating the full-expression ([intro.execution]) that (lexically) contains the point where they were created.
But... what is the "full-expression" here?
One would assume it would be the promise.get_result_object() expression. But is that a "full-expression" by C++'s rules? These rules are rather esoteric and technical. One could argue that promise.get_result_object() is being used to initialize an object and therefore it is de-facto an "init-declarator". But "init-declarator" is a piece of grammar, and promise.get_result_object() is not stated by the text to be an "init-declarator".
An argument could be made that the only expression that is certainly a full expression is coroutine(). And therefore, one could make the argument that any temporaries used to initialize the return value object should persist until control returns to the caller.
I would argue that the standard wording is under-specified and therefore both versions are equally legitimate until clarification is provided. Clang's version makes more sense (but not for the reasons you claim), but the others are at least arguable.
The code below:
int i = 1;
const int i_c = 2;
volatile int i_v = 3;
const volatile int i_cv = 4;
typedef std::variant<int, const int, volatile int, const volatile int> TVariant;
TVariant var (i );
TVariant var_c (i_c );
TVariant var_v (i_v );
TVariant var_cv(i_cv);
std::cerr << std::boolalpha;
std::cerr << std::holds_alternative< int>(var ) << std::endl;
std::cerr << std::holds_alternative<const int>(var_c ) << std::endl;
std::cerr << std::holds_alternative< volatile int>(var_v ) << std::endl;
std::cerr << std::holds_alternative<const volatile int>(var_cv) << std::endl;
std::cerr << var .index() << std::endl;
std::cerr << var_c .index() << std::endl;
std::cerr << var_v .index() << std::endl;
std::cerr << var_cv.index() << std::endl;
outputs:
true
false
false
false
0
0
0
0
coliru
And so std::variant converting constructor doesn't take into account const volatile qualifier of the converting-from type. Is it expected behavior?
Information about converting constructor from cppreference.com
Constructs a variant holding the alternative type T_j that would be selected by overload resolution for the expression F(std::forward<T>(t)) if there was an overload of imaginary function F(T_i) for every T_i from Types...
The problem is that in the case above the overload set of such imaginary function is ambiguous:
void F( int) {}
void F(const int) {}
void F( volatile int) {}
void F(const volatile int) {}
coliru
cppreference.com says nothing about this case. Does the standard specify this?
I'm making my own implementation of std::variant class. My implementation of converting constructor is based on this idea. And the result is the same as shown above (the first suitable alternative is selected, even though there are others). libstdc++ probably implements it in the same way, because it also selects the first suitable alternative. But I'm still wondering if this is correct behavior.
Yeah, this is just how functions work when you pass by value.
The function void foo(int) and the function void foo(const int) and the function void foo(volatile int) and the function void foo(const volatile int) are all the same function.
By extension, there is no distinction for your variant's converting constructor to make, and no meaningful way to use a variant whose alternatives differ only in their top-level cv-qualifier.
(Well, okay, you can emplace with an explicit template argument, as Marek shows, but why? To what end?)
[dcl.fct/5] [..] After producing the list of parameter types, any top-level cv-qualifiers modifying a parameter type are deleted when forming the function type. [..]
Note that you are creating copy of value. This means that const and volatile modifiers can be safely discarded. That is why template always deduces int.
You can force specific type using emplace.
See demo https://coliru.stacked-crooked.com/a/4dd054dc4fa9bb9a
My reading of the standard is that the code should be ill-formed due to ambiguity. It surprises me that both libstdc++ and libc++ appear to allow it.
Here's what [variant.ctor]/12 says:
Let T_j be a type that is determined as follows: build an imaginary function FUN(T_i) for each alternative type T_i. The overload FUN(T_j) selected by overload resolution for the expression FUN(std::forward<T>(t)) defines the alternative T_j which is the type of the contained value after construction.
So four functions are created: initially FUN(int), FUN(const int), FUN(volatile int), and FUN(const volatile int). These are all equivalent signatures, so they could not be overloaded with each other. This paragraph does not really specify what should happen if the overload set cannot actually be built. However, there is a note that strongly implies a particular interpretation:
[ Note:
variant<string, string> v("abc");
is ill-formed, as both alternative types have an equally viable constructor for the argument. —end note]
This note is basically saying that overload resolution cannot distinguish between string and string. In order for that to happen, overload resolution must be done even though the signatures are the same. The two FUN(string)s are not collapsed into a single function.
Note that overload resolution is allowed to consider overloads with identical signatures due to templates. For example:
template <class T> struct Id1 { using type = T; };
template <class T> struct Id2 { using type = T; };
template <class T> void f(typename Id1<T>::type x);
template <class T> void f(typename Id2<T>::type x);
// ...
f<int>(0); // ambiguous
Here, there are two identical signatures of f, and both are submitted to overload resolution but neither is better than the other.
Going back to the Standard's example, it seems that the prescription is to apply the overload resolution procedure even if some of the overloads could not be overloaded with each other as ordinary function declarations. (If you want, imagine that they are all instantiated from templates.) Then, if that overload resolution is ambiguous, the std::variant converting constructor call is ill-formed.
The note does not say that the variant<string, string> example was ill-formed because the type selected by overload resolution occurs twice in the list of alternatives. It says that the overload resolution itself was ambiguous (because the two types had equally viable constructors). This distinction is important. If this example were rejected after the overload resolution stage, an argument could be made that your code is well-formed since the top-level cv-qualifiers would be deleted from the parameter types, making all four overloads FUN(int) so that T_j = int. But since the note suggests a failure during overload resolution, that means your example is ambiguous (as the 4 signatures are equivalent) and this must be diagnosed.
In DR 2137 we have the following text (emphasis is mine):
It is not clear in code like the following that selecting a copy/move
constructor is the correct choice when an initializer list contains a
single element of the type being initialized, as required by issue
1467:
#include <initializer_list>
#include <iostream>
struct Q {
Q() { std::cout << "default\n"; }
Q(Q const&) { std::cout << "copy\n"; }
Q(Q&&) { std::cout << "move\n"; }
Q(std::initializer_list<Q>) { std::cout << "initializer list\n"; }
};
int main() {
Q x = Q { Q() };
}
Here the intent is that Q objects can contain other Q objects, but
this is broken by the resolution of issue 1467.
I'd like to understand why the code above would be broken by the resolution of issue 1467.
DR 2147 has this statement: "the intent is that Q objects can contain other Q objects". Given that, it assumes that if the user constructs a Q from a braced-init-list containing other Qs, that the intent of the user is to call the initializer_list constructor.
Given that assumption, it is therefore "broken" to not call the initializer_list constructor, which 1467 would cause.
Whether you agree with this logic or not, that's the thinking behind 2147. It's also the thinking that permits [over.match.list] to prioritize initializer_list constructors over any other constructor type. So in that way, it is consistent.
Consider the following class:
class foo {
int data;
public:
template <typename T, typename = enable_if_t<is_constructible<int, T>::value>>
foo(const T& i) : data{ i } { cout << "Value copy ctor" << endl; }
template <typename T, typename = enable_if_t<is_constructible<int, T>::value>>
foo(T&& i) : data{ i } { cout << "Value move ctor" << endl; }
foo(const foo& other) : data{ other.data } { cout << "Copy ctor" << endl; }
foo(foo&& other) : data{ other.data } { cout << "Move ctor" << endl; }
operator int() { cout << "Operator int()" << endl; return data; }
};
Of course it doesn't make much sense to take a single int by any kind of reference, but this is just an example. The data member could be very expensive to copy, hence all the move semantics.
That fancy template basically enables any type from which data can be constructed. So a foo object can be constructed either by copying or moving a value of any type that satisfies this criteria, or simply by copying or moving another object of type foo. Pretty straight forward so far.
The problem occurs when you try to do something like this:
foo obj1(42);
foo obj2(obj1);
What this should do (at lest in my opinion) is to construct the first object by moving the value 42 into it (since it's an rvalue), and then construct the second object by copying the first object. So what this should print out is:
Value move ctor
Copy ctor
But what it actually prints out is:
Value move ctor
Operator int
Value move ctor
The first object gets constructed just fine, no problem there. But instead of calling the copy constructor to construct the second object, the program converts the first object into another type (via the conversion we defined) and then calls another constructor of foo which can move from that type (since it's an rvalue at that point).
I find this very strange, and it's definitely not the behavior I would want from this piece of code. I think it makes more sense to just call the copy constructor upon constructing the second object, as that seems way more trivial given the type of argument I supplied.
Can anyone explain what happens here? Of course I understand that since there's a user-defined conversion to int, this is a perfectly valid path to take, but I cannot make sense of it. Why would the compiler refuse to simply call the constructor which has the exact same argument type as the supplied value? Wouldn't that be the most trivial thing to do, therefore the default behavior? Calling the conversion operator does perform a copy as well, so I don't think that is faster or more optimal than simply calling the copy constructor either.
Your template "move" constructor (with T = foo &) has a parameter of type foo &, which is a better match than your copy constructor, since that only takes const foo &. Your template constructor then fills data by converting i to int, invoking operator int().
The simplest immediate fix could be to use enable_if to restrict your move constructor to move operations: if T is deduced as an lvalue reference type (meaning your T&& i would collapse to an lvalue reference too), force a substitution failure.
template <typename T, typename = enable_if_t<is_constructible<int, T>::value>,
typename = enable_if_t<!is_lvalue_reference<T>::value>>
foo(T&& i) : data( std::move(i) ) { cout << "Value move ctor" << endl; }
Note: since your parameter has a name, inside the constructor, just like all other named objects, it's an lvalue. Given that you want to move from it, you can use std::move.
More generally, you could use perfect forwarding (accepting both lvalues and rvalues), and only remove foo itself as a special exception:
template <typename T, typename = enable_if_t<is_constructible<int, T>::value>
, typename = enable_if_t<!is_same<decay_t<T>, foo>::value>
foo(T&& i) : data( std::forward<T>(i) ) { cout << "Forwarding ctor" << endl; }
This would replace your value copy and value move constructor.
Another note: is_constructible<int, T>::value is a test that tells you whether data(std::forward<T>(i)) would be well-formed. It does not test whether data{std::forward<T>(i)} would be well-formed. A T for which the result is different is long, since the long to int conversion is a narrowing conversion, and narrowing conversions are not allowed in {}.
It happens because of operator int().
because of operator int(), obj1 calls operator int() and casts itself as an int.
Possible solutions:
use static_cast<foo>. this will cast the variable that normally should cast as an int, to a foo.
Anyone please edit this question and fill this. I have no more ideas.
If I call the "func(const generic& ref)" with an integer as argument (instead of a 'generic' object), the constructor generic(int _a) will be called to create a new object.
class generic {
public:
int a;
generic() {}
generic(int _a) : a(_a) {
std::cout << "int constructor was called!";
}
generic(const generic& in) : a(in.a) {
std::cout << "copy constructor was called!";
}
};
void func(const generic& ref) {
std::cout << ref.a;
}
int main() {
generic g(2);
func(g); // this is good.
func(generic(4)); // this is good.
func(8); // this is good...... ?
return 0;
}
The last "func(8)" call creates a new object using the constructor generic(int _a). Is there a name for this kind of construction? Shouldn't the programmer explicitly construct an object before passing the argument? Like this:
func(generic(8));
Is there any benefit in passing the integer alone (other than saving time)?
This behavior is part of overload resolution process - specifically.
When you call func(), the compiler constructs a list of candidates. There's only one candidate, func(const generic& ref), so the compiler tries to figure out how to make a call.
It sees that there is no func(int) defined, so it tries to find a conversion path from int to generic. Since there is a constructor of generic that takes an int, and there are no other conversions allowing to perform the same call, the compiler takes the construction+call path.
The compiler checks three things, in the order from higher to lower priority:
Exact match
Promotion
Conversion
This means that an exact match of the signature trumps a match that requires a promotion, and a match that requires a promotion trumps a match that needs a conversion.
See "Ranking of implicit conversion sequences" of the document linked above for information on how implicit conversions are applied.
Is there a name for this kind of construction? Shouldn't the programmer explicitly construct an object before passing the argument?
If you don't want this to happen, you can add the explicit specifier to your constructor:
explicit generic(int _a) : a(_a)
{
std::cout << "int constructor was called!";
}
An excerpt from the cppreference page:
A constructor that is declared without the function specifier explicit is called a converting constructor.
By default, implicit constructor calls are allowed in this circumstance.
Is there any benefit in passing the integer alone (other than saving time)?
Whether you call the method with func(8) or func(generic(8)) isn't going to change what code executes given the code you have written. If you were to add an overload of func that takes an int instead of a generic, then the calls would suddenly become different. So, although it is ultimately a matter of opinion, I think you are better off being explicit by using func(generic(8)).