Universal references and std::initializer_list - c++

In his "C++ and Beyond 2012: Universal References" presentation, Scott repeatedly stresses the point, that universal references handle/bind to everything and thus overloading a function that already takes a universal reference parameter does not make sense.
I had no reason to doubt that until I mingled them with std::initializer_list.
Here is a short example:
#include <iostream>
#include <initializer_list>
using namespace std;
template <typename T>
void foo(T&&) { cout << "universal reference" << endl; }
template <typename T>
void foo(initializer_list<T>) { cout << "initializer list" << endl; }
template <typename T>
void goo(T&&) { cout << "universal reference" << endl; }
template <typename T>
void goo(initializer_list<T> const&) { cout << "initializer list" << endl; }
int main(){
auto il = {4,5,6};
foo( {1,2,3} );
foo( il );
goo( {1,2,3} );
goo( il );
return 0;
}
Oddly enough, VC11 Nov 2012 CTP complains about ambiguity (error C2668: 'foo' : ambiguous call to overloaded function). Yet even more suprising is, that gcc-4.7.2, gcc-4.9.0 and clang-3.4 agree on the following output:
initializer list
initializer list
initializer list
universal reference
So apparently it is possible (with gcc and clang) to overload functions taking universal references with initializer_lists but when using the auto + { expr } => initializer_list-idiom it does even matter whether one takes the initializer_list by value or by const&.
At least to me that behavior was totally surprising.
Which behavior conforms to the standard? Does anyone know the logic behind that?

Here's the crux: Deducing a type from a braced-init-list ({expr...}) doesn't work for template arguments, only auto. With template arguments, you get a deduction failure, and the overload is removed from consideration. This leads to the first and third output.
it does even matter whether one takes the initializer_list by value or by const&
foo: For any X, two overloads taking X and X& parameters are ambiguous for an lvalue argument - both are equally viable (same for X vs X&& for rvalues).
struct X{};
void f(X);
void f(X&);
X x;
f(x); // error: ambiguous overloads
However, partial ordering rules step in here (§14.5.6.2), and the function taking a generic std::initializer_list is more specialized than the generic one taking anything.
goo: For two overloads with X& and X const& parameters and a X& argument, the first one is more viable because the second overload requires a Qualification conversion from X& to X const& (§13.3.3.1.2/1 Table 12 and §13.3.3.2/3 third sub-bullet).

If Scott really says that he's wrong, and it's another problem with the misleading "universal references" mental model he's teaching.
So-called "universal references" are greedy, and might match when you don't want or expect them to, but that doesn't mean they are always the best match.
Non-template overloads can be an exact match and will be preferred to the "universal reference", e.g. this selects the non-template
bool f(int) { return true; }
template<typename T> void f(T&&) { }
bool b = f(0);
And template overloads can be more specialized than the "universal reference" and so will be chosen by overload resolution. e.g.
template<typename T> struct A { };
template<typename T> void f(T&&) { }
template<typename T> bool f(A<T>) { return true; }
bool b = f(A<int>());
DR 1164 confirms that even f(T&) is more specialized than f(T&&) and will be preferred for lvalues.
In two of your cases the initializer_list overloads are not only more specialized, but a braced-init-list such as {1,2,3} can never be deduced by template argument deduction.
The explanation for your results is:
foo( {1,2,3} );
You cannot deduce a template argument from a braced-init-list, so deduction fails for foo(T&&) and foo(initializer_list<int>) is the only viable function.
foo( il );
foo(initializer_list<T>) is more specialized than foo(T&&) so is chosen by overload resolution.
goo( {1,2,3} );
You cannot deduce a template argument from a braced-init-list, so goo(initializer_list<int>) is the only viable function.
goo( il );
il is a non-const lvalue, goo(T&&) can be called with T deduced as initializer_list<int>&, so its signature is goo(initializer_list<int>&) which is a better match than goo(initializer_list<int> const&) because binding the non-const il to a const-reference is a worse conversion sequence than binding it to a non-const-reference.
One of the comments above quotes Scott's slides as saying: "Makes no sense: URefs handle everything." That's true, and that's exactly why you might want to overload! You might want a more specific function for certain types, and the universal reference function for everything else. You can also use SFINAE to constrain the universal reference function to stop it handling certain types, so that other overloads can handle them.
For an example in the standard library, std::async is an overloaded function taking universal references. One overload handles the case where the first argument is of type std::launch and the other overload handles everything else. SFINAE prevents the "everything else" overload from greedily matching calls that pass std::launch as the first argument.

Ok, so first the reaction to foo makes sense. initializer_list<T> match both calls and is more specialized, therefore should be called this way.
For goo, this is in sync with perfect forwarding. when calling goo(il), there is the choice between goo(T&&) (with T = initializer_list<T>&) and the constant reference version. I guess calling the version with the non-const reference has precedence over the more specialized version with the const reference. That being said, I am not sure this is a well defined situation w.r.t. the standard.
Edit:
Note that if there were no template, that would be resolved by the paragraph 13.3.3.2 (Ranking implicit conversion sequences) of the standard. The problem here is that, AFAIK, the partial ordering of template function would dictate the second (more specialized) goo(initializer_list<T> const&) is to be called, but the ranking on implicit conversion sequences would dictate that goo(T&&) is to be called. So I guess this is an ambiguous case.

Related

Is it possible to omit template parameter in `std::forward`?

The standard signature of std::forward is:
template<typename T>
constexpr T&& forward(std::remove_reference_t<T>&) noexcept;
template<typename T>
constexpr T&& forward(std::remove_reference_t<T>&&) noexcept;
Because the parameter type isn't T directly, we should specify the template argument when using std::forward:
template<typename... Args>
void foo(Args&&... args)
{
bar(std::forward<Args>(args)...);
}
However sometimes the template argument is not as simple as Args. auto&& is a case:
auto&& vec = foo();
bar(std::forward<decltype(vec)>(vec));
You can also imagine more complicated template argument types for std::forward. Anyway, intuitively std::forward should know what T is but it actually don't.
So my idea is to omit <Args> and <decltype(vec)> no matter how simple they are. Here is my implementation:
#include <type_traits>
template<typename T>
std::add_rvalue_reference_t<std::enable_if_t<!std::is_lvalue_reference<T>::value, T>>
my_forward(T&& obj)
{
return std::move(obj);
}
template<typename T>
T& my_forward(T& obj)
{
return obj;
}
int main()
{
my_forward(1); // my_forward(int&&)
int i = 2;
my_forward(i); // my_forward(int&)
const int j = 3;
my_forward(j); // my_forward(const int&)
}
When obj is rvalue reference, for example int&&, the first overload is selected because T is int, whose is_lvalue_reference is false;
When obj is lvalue reference, for example const int&, the second overload is selected because T is const int& and the first is SFINAE-ed out.
If my implementation is feasible, why is std::forward still requiring <T>? (So mine must be infeasible.)
If not, what's wrong? And still the question, is it possible to omit template parameter in std::forward?
The problematic case is when you pass something of rvalue reference type but which does not belong to an rvalue value category:
int && ir{::std::move(i)};
my_forward(ir); // my_forward(int&)
Passing type to std::forward will ensure that arguments of rvalue reference types will be moved further as rvalues.
The answer by user7860670 gives you an example for the case where this breaks down. Here is the reason why the explicit template parameter is always needed there.
By looking at the value of the forwarding reference you can no longer reliably determine through overload resolution whether it is safe to move from. When passing an lvalue reference parameter as an argument to a nested function call it will be treated as an lvalue. In particular, it will not bind as an rvalue argument, that would require an explicit std::move again. This curious asymmetry is what breaks implicit forwarding.
The only way to decide whether the argument should be moved onwards is by inspecting its original type. But the called function cannot do so implicitly, which is why we must pass the deduced type explicitly as a template parameter. Only by inspecting that type directly can we determine whether we do or do not want to move for that argument.

std::variant converting constructor doesn't handle const volatile qualifiers

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.

Why doesn't universal reference apply for arrays?

#include <type_traits>
template<typename T>
void f(const T&)
{
static_assert(std::is_array_v<T>); // ok
}
template<typename T>
void g(T&&)
{
static_assert(std::is_array_v<T>); // error
}
int main()
{
char arr[8];
f(arr); // ok
g(arr); // error
}
My compiler is clang 7.0 with -std=c++17.
Why doesn't universal reference apply for arrays?
First of all, these are officially called "forwarding references", not "universal references".
Your static_assert fails due to the fact that T is deduced as T& when passing an lvalue to a function taking a "forwarding reference" - this is one of the special rules of "forwarding references" that apply during template argument deduction.
You can fix your assert by stripping any reference out first:
static_assert(std::is_array_v<std::remove_cvref_t<T>>);
live example on godbolt.org
std::remove_cvref_t is a bleeding edge C++20 feature - you might want to use std::remove_reference_t instead if your compiler doesn't support it.
The relevant rule here when the template argument deduction takes place is:
Deduction from a function call
...
4. If P is an rvalue reference to a cv-unqualified template parameter (so-called forwarding reference), and the corresponding function call argument is an lvalue, the type lvalue reference to A is used in place of A for deduction.
So in your case, arr is being deduced as reference to arr and thus the static_assert fails.

How is the move constructor of member variable invoked without using std::forward?

An example here for std::forward,
// forward example
#include <utility> // std::forward
#include <iostream> // std::cout
// function with lvalue and rvalue reference overloads:
void overloaded (const int& x) {std::cout << "[lvalue]";}
void overloaded (int&& x) {std::cout << "[rvalue]";}
// function template taking rvalue reference to deduced type:
template <class T> void fn (T&& x) {
overloaded (x); // always an lvalue
overloaded (std::forward<T>(x)); // rvalue if argument is rvalue
}
int main () {
int a;
std::cout << "calling fn with lvalue: ";
fn (a);
std::cout << '\n';
std::cout << "calling fn with rvalue: ";
fn (0);
std::cout << '\n';
return 0;
}
Output:
calling fn with lvalue: [lvalue][lvalue]
calling fn with rvalue: [lvalue][rvalue]
mentions that
the fact that all named values (such as function parameters) always
evaluate as lvalues (even those declared as rvalue references)
Whereas, the typical move constructor looks like
ClassName(ClassName&& other)
: _data(other._data)
{
}
which looks like _data(other._data) should invoke the move constructor of _data's class. But, how is it possible without using std::forward? In other words, shouldn't it be
ClassName(ClassName&& other)
: _data(std::forward(other._data))
{
}
?
Because, as pointed out in std:forward case,
all then named values should evaluate as lvalue
I more and more like C++ because of the depth of issue like this and the fact that the language is bold enough to provide such features :) Thank you!
A typical move constructor looks like this (assuming it is explicitly implemented: you might want to prefer = default):
ClassName::ClassName(ClassName&& other)
: _data(std::move(other._data)) {
}
Without the std::move() the member is copied: since it has a name other is an lvalue. The object the reference is bound to is an rvalue or an object considered as such, however.
std::forward<T>(obj) is always used with an explicit template argument. In practice the type is that deduced for a forwarding reference. These look remarkably like rvalue references but are something entirely different! In particular, a forwarding reference may refer to an lvalue.
You may be interested in my Two Daemons article which describes the difference in detail.
std::forward should be used with a forwarding reference.
std::move should be used with an rvalue reference.
There is nothing particular about constructors. The rules apply the same to any function, member function or constructor.
The most important thing is to realize when you have a forwarding reference and when you have an rvalue reference. They look similar but are not.
A forwarding reference is always in the form:
T&& ref
for T some deduced type.
For instance, this is a forwarding reference:
template <class T>
auto foo(T&& ref) -> void;
All these are rvalue references:
auto foo(int&& ref) -> void; // int not deduced
template <class T>
auto foo(const T&& ref); // not in form `T&&` (note the const)
template <class T>
auto foo(std::vector<T>&& ref) -> void; // not in form `T&&`
template <class T>
struct X {
auto foo(T&& ref) -> T; // T not deduced. (It was deduced at class level)
};
For more please check this excellent in-depth article by Scott Meyers with the note that when the article was written the term "universal reference" was used (actually introduced by Scott himself). Now it is agreed that "forwarding reference" better describes it's purpose and usage.
So your example should be:
ClassName(ClassName&& other)
: _data(std::move(other._data))
{
}
as other is an rvalue reference because ClassName is not a deduced type.
This Ideone example should make things pretty clear for you. If not, keep reading.
The following constructor accepts Rvalues only. However, since the argument "other" got a name it lost its "rvalueness" and now is a Lvalue. To cast it back to Rvalue, you have to use std::move. There's no reason to use std::forward here because this constructor does not accept Lvalues. If you try to call it with a Lvalue, you will get compile error.
ClassName(ClassName&& other)
: _data(std::move(other._data))
{
// If you don't use move, you could have:
// cout << other._data;
// And you will notice "other" has not been moved.
}
The following constructor accepts both Lvalues and Rvalues. Scott Meyers called it "Universal Rerefences", but now it's called "Forwarding References". That's why, here, it's a must to use std::forward so that if other was an Rvalue, _data constructor will get called with an Rvalue. If other was an Lvalue, _data will be constructed with an Lvalue. That's why it's called perfect-forwarding.
template<typename T>
ClassName(T&& other)
: _data(std::forward<decltype(_data)>(other._data))
{
}
I've tried to use your constructors as an example so you could understand, but this is not specific to constructors. This applies to functions as well.
With the first example tho, since your first constructor only accepts Rvalues, you could perfectly use std::forward instead, and both would do the same thing. But it's best not to do it, because people may think that your constructor accepts a forwarding reference, when it actually doesn't.

Templated operator overload resolution, member vs non-member function

When trying out clang-3.4 (compiled from git), it failed to compile one of my projects complaining about ambiguity while resolving overloaded operators.
I turned out that there were two templated operators, one of which was declared as a member function, other as a non-member one, which both seems equally good match.
Following SSCCE demonstrates the situation:
#include <iostream>
struct ostr {
std::ostream& s;
template<class T>
ostr& operator<<(const T& x) { s << x; return *this; }
};
struct xy {
double x, y;
};
template<class Stream>
Stream& operator<<(Stream& s, const xy& x) {
s << "[" << x.x << ", " << x.y << "]";
return s;
}
int main() {
ostr os{std::cout};
xy x{4, 5};
os << "Value is: " << x <<"\n";
}
The project compiled fine before and I checked again this SSCCE with several compilers (gcc 4.5, 4.6, 4.7, 4.8 and clang 3.3) and all of them compiled it without any warning (with -Wall -Wextra -pedantic). All compilers were set to C++11/C++0x standard.
After adding ctor to ostr, it compiled fine even on MSVC 2012 and 2010)
Making both operator<<s non-member exhibits the ambiguity in all compilers (as expected)
After looking through the standard drafts (N3242 and N3690) I failed to find anything making member functions/operators better match than non-member ones.
So I failed to prove clang-3.4 is wrong and I wonder who's right.
Thus my question is:
Is this code valid? Should member operators/functions be better match than non-member ones and it's a bug in clang-3.4?
Or are all the other compilers wrong/too permissive?
I am aware that changing the second operator<< to non-templated function (with std::ostream instead of template parameter) would resolve the the ambiguity and work as expected, but that's not the point here.
Overload resolution adds an additional parameter to a member function just for the purpose of overload resolution:
[over.match.funcs]/2
The set of candidate functions can contain both member and non-member functions to be resolved against the same argument list. So that argument and parameter lists are comparable within this heterogeneous set, a member function is considered to have an extra parameter, called the implicit object parameter, which represents the object for which the member function has been called.
/4
For non-static member functions, the type of the implicit object parameter is
— “lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier
— “rvalue reference to cv X” for functions declared with the && ref-qualifier
where X is the class of which the function is a member and cv is the cv-qualification on the member function declaration.
Some special rules follow, for example to allow binding an rvalue to this implicit object parameter (for calling member functions w/o ref-qualifier on rvalues, e.g. ostr{std::cout}<<"hello").
The function signatures including the implicit object parameter we need to compare for overload resolution are:
template<class T>
ostr& ostr::operator<<(ostr&, const T&); // F1
template<class Stream>
Stream& ::operator<<(Stream&, const xy&); // F2
After substitution for os << x, we get the same signature:
ostr& ostr::operator<<(ostr&, const xy&);
ostr& :: operator<<(ostr&, const xy&);
So only one of the "tie-breakers" in [over.match.best]/1 could resolve the ambiguity. Indeed, one could apply, namely the "F1 is more specialized than F2" (or vice versa): partial ordering of function templates.
N.B. The procedure of adding an implicit object parameter is specified again in the description of partial ordering [temp.func.order]/3.
For partial ordering of F1 and F2 (as defined above), we first create two unique types:
struct unique_T {};
struct unique_Stream {};
Then we transform F1 into F1' by replacing the template parameter T with the unique type unique_T (and similarly for F2):
ostr& ostr::operator<<(ostr&, const unique_T&);
ostr& :: operator<<(unique_Stream&, const xy&);
The parameters of the transformed function F1' are now used to try to deduce the template parameters of the untransformed F2:
ostr a0;
unique_T a1; // no reference, no cv-qualifier
::operator<<(a0, a1) // does template argument deduction succeed?
// reminder: signature of ::operator<<
template<class Stream>
Stream& ::operator<<(Stream&, const xy&);
The deduction succeeds for a0 [with Stream = ostr], therefore the type ostr& from F1 is considered to be at least as specialized as type of the corresponding first parameter of F2 (Stream&, with Stream being a template parameter). I'm not sure what happens to the second argument a1, since no deduction takes place for the second parameter of ::operator<< (it is of type const xy&).
Now we repeat the process with arguments from F2' and try to deduce the template parameters of F1:
unique_Stream a0;
xy a1;
ostr::operator<<(a0, a1);
// reminder: signature of ostr::operator<<
template<class T>
ostr& ostr::operator<<(ostr&, const T&);
Here, no deduction occurs for the first argument, but it occurs and succeeds for the second argument [with T = xy].
I conclude no function template is more specialized. Therefore overload resolution should fail due to ambiguity.