I was just trying to learn the new c++17 changes related "auto rules for direct-list-initialization"
Few of the stackoverflow question thread has answers like its not a safe thing to do
Why is direct-list-initialization with auto considered bad or not preferred?
Tried one of the selected answer to understand
#include <typeinfo>
#include <iostream>
struct Foo{};
void eatFoo (const Foo& f){}
int main() {
Foo a;
auto b{a};
eatFoo(b);
std::cout << "a " << typeid(a).name() << '\n';
std::cout << "b " << typeid(b).name() << '\n';
}
But to my surprise it compiled without any warning or compile error
output
a 3Foo
b 3Foo
Program ended with exit code: 0
Does it mean now its safe to use auto for direct initialization
for example like this
auto x { 1 };
"Safe" is in the eye of the beholder.
C++14's behavior was "safe", in that it was well-defined what the result would be. auto var_name{expr} would create an initializer_list of one element.
C++17 makes it so that auto var_name{expr} results in the same type deduction as auto var_name = expr; would have. So long as you expect this to be the case, then it is "safe". But it is no more "safe" than the old behavior.
Then again, this is a language change that is backwards-incompatible with the C++14 behavior. By that standard, it is not "safe", in that a C++14 user will expect your code to be doing something different than it is under a C++17 compiler. So it creates some subtle confusion, which may not be safe.
Then there's the issue that "uniform initialization" is supposed to be "uniform" (ie: performs the same kind of initialization under the same rules in all places), yet auto var_name{expr} will do something very different from auto var_name = {expr}. The latter will still be an initializer_list; the former will be a copy/move. By that token, it is not "safe" to assume that "uniform initialization" is uniform.
Then again, the C++17 behavior is sometimes the thing you really wanted. That initializing a variable from a single value, to you, means to copy/move it. After all, if you had done decltype(expr) var_name{expr}, that's the behavior you'd get. So from that perspective, it could seem to be "safe" behavior.
At the very least, we can say that the situation is more teachable through two rules:
Direct-list-initialization of auto variables means "copy/move the expression into the variable, and if you provide more than one expression, you're using the wrong syntax."
Copy-list-initialization of auto variables means "make an initializer_list, and if you provide values that can't do that, you're using the wrong syntax."
Perhaps the simplicity creates a sense of "safety".
The document that instigated these changes makes a case that you're less likely to get accidental UB by returning a stack-bound initializer_list by sticking to the above rules. That's one definition of "safe".
So it all depends on what you mean by "safe".
Related
Just a beginner question.
In the code from cppreference.com, there is a comment saying, "make sure it's a side effect" on the line using std::accumulate.
What is the author's intention in saying this? Is it related to the volatile int sink? (Maybe it means something like "make sure the sink is not optimized by the compiler"?)
Why volatile for sink? I think almost every compiler might know the std::accumulate will change the value of sink.
I thought I knew what volatile and "side-effect" meant, but maybe I'm missing some point!
#include <iostream>
#include <iomanip>
#include <vector>
#include <numeric>
#include <chrono>
volatile int sink;
int main()
{
std::cout << std::fixed << std::setprecision(9) << std::left;
for (auto size = 1ull; size < 1000'000'000ull; size *= 100) {
// record start time
auto start = std::chrono::system_clock::now();
// do some work
std::vector<int> v(size, 42);
// This is the line I mentioned.
sink = std::accumulate(v.begin(), v.end(), 0u); // make sure it's a side effect
// record end time
auto end = std::chrono::system_clock::now();
std::chrono::duration<double> diff = end - start;
std::cout << "Time to fill and iterate a vector of " << std::setw(9)
<< size << " ints : " << diff.count() << " s\n";
}
}
Not necessarily.
C++ has the 'as if' rule. The compiler must generate code that works 'as if' the source code was executed, but it doesn't have to do everything that the source code does, in exactly the same order.
Now look at the sink variable. After the line you mention it's never used again. So it's value has no visible effect on the program. So why should the compiler bother calculating it's value? Because of the as if rule it is perfectly legal for the compiler not to do so.
But there are a few exceptions to the as if rule, and one of them is volatile variables. Reads and writes to volatile variables must occur in exactly the way that the source code says. That's why volatile is important in this code. It forces the compiler to execute the std::accumulate call even though it has no visible effect on the program.
Further reading https://en.cppreference.com/w/cpp/language/as_if
The issue in the code sample given is that the value of sink is never used after it has been calculated. Thus, a compiler would be free to "optimize out" the call to std::accumulate, as doing so would have no observable effect.
Adding the volatile qualifier to sink prevents such an undesired optimization. From another cppreference page (bold emphasis mine):
volatile object - An object whose type is volatile-qualified, or a subobject of a
volatile object, or a mutable subobject of a const-volatile object.
Every access (read or write operation, member function call, etc.) made through a glvalue expression of volatile-qualified type
is treated as a visible side-effect for the purposes of optimization (that is, within a single thread of execution,
volatile accesses cannot be optimized out or reordered with another
visible side effect that is sequenced-before or sequenced-after the
volatile access. …)
The example code referenced is intended to show timing functions.
The C++ standard allows the compiler to look at some code, and remove it, if it believes the result is not used.
The example uses volatile and the side effect to be sure that the timing is not optimized away by the compiler.
int c = 2;
int d = std::move(c);
std::cout << "c is: " << c << std::endl;
std::cout << "d is: " << d << std::endl;
this code output:
c is: 2
d is: 2
I thought that after move(c) to d, c will be empty, why does it still have 2 as its value ? Can you anyone please help me explain this ? Thank you.
I thought that after move(c) to d, c will be empty,
Your expectation was mis informed.
why does it still have 2 as its value ?
Fundamental types do not have move constructors. You have simply made a copy. Copying does not modify the source object.
For class types, it would not be safe to assume what the move constructor does exactly, and specifically what state the source object is left in. It is not necessarily guaranteed to be "empty". See the documentation of the class for what it does. If there is no documentation, or documentation doesn't give any guarantees, then you cannot assume anything about the state of the source object.
std::move doesn't move anything! (contrary to it's name). It is exactly equivalent to a static_cast to an rvalue reference type.
That it, it is just a cast to rvalue- more specifically to an xvalue, as opposed to a prvalue. And it is also true that having a cast named move sometimes confuses people. However the intent of this naming is not to confuse, but rather to make your code more readable.
Using the xvalue, we can trigger the right overload and hence, we can use std::swap in such overloads to take the ownership of another object (but aren't required).
For example, a move constructor of a linked list might copy the pointer to the head of the list and store nullptr in the argument instead of allocating and copying individual nodes.
why does it still have 2 as its value
As mentioned std::move doesn't move and the real job of swapping/moving the resources is being performed by the overloads like move constructor and move assignment. std::move tasks is just to cast so that compiler can call the right overload (for example, move constructor in favor of copy constructor) and the actual moving of resources has to be defined by the software developer in their respective overloads. Since, fundamental types like int doesn't have any move constructor, the statement int c = std::move(a); merely copies the value of a to c.
Try this:
#include <iostream>
#include <utility>
void hello(int& a)
{
std::cout << "LVALUE" << std::endl;
}
void hello(int&& a)
{
std::cout << "RVALUE" << std::endl;
}
int main(void)
{
int a = 8;
hello(a);
hello(std::move(a));
return 0;
}
First, as mentioned by #eerorika. Moving fundamental types is equivalent to copying. The reason of this behavior is pretty clear. Move semantic is developed for saving computational resource, and you clearly saved nothing (but wasted something) by clearing the value of an integer variable, which is not going to be further used. Leave it there is the best here.
Second, a "moved" variable is not necessarily "empty" or "cleared". It may be in any status formally, but for standard library objects there are some guarantees: (Quoted from here)
Unless otherwise specified, all standard library objects that have
been moved from are placed in a valid but unspecified state. That is,
only the functions without preconditions, such as the assignment
operator, can be safely used on the object after it was moved from
As a result, you may see a "moved" std::vector contains random values and it is perfectly correct. Never assume such a std::vector is (or is not) empty because it might yields undefined behaviours. More generally, make no assumption (except the status is valid for standard library object) about the status of a object that was moved from.
As far as I know, std::to_integer<T> is equivalent to T(value) where value is a variable having type std::byte.
I looked into some implementations from the major compilers and found that in this case equivalent means literally implemented as. In other terms, most of the times to_integer is actually implemented as:
return T(value);
And that's all.
What I don't understand is what's the purpose of such a function?
Ironically the cons are even more than the pros. I should include a whole header for such a function just to avoid a C-like cast that is most likely directly inlined anyway.
Is there any other reason for that or it's just really a nice looking alternative for a C-like cast and nothing more?
it's just really a nice looking alternative for a C-like cast and nothing more?
You say that as though it's some trivial detail.
Casts are dangerous. It's easy to cast something to the wrong type, and often compilers won't stop you from doing exactly that. Furthermore, because std::byte is not an integral type in C++, working with numerical byte values often requires a quantity of casting. Having a function that explicitly converts to integers makes for a safer user experience.
For example, float(some_byte) is perfectly legal, while to_integer<float>(some_byte) is explicitly forbidden. to_integer<T> requires that T is an integral type.
to_integer is a safer alternative.
I should include a whole header for such a function
If by "whole header", you mean the same header you got std::byte from and therefore is already by definition included...
std::to_integer<T>(some_byte) is equivalent to T(some_byte) if it actually compiles. T(some_byte) is equivalent to the unsafe C-style cast of (T)some_byte, which can do scary things. On the other hand, std::to_integer is appropriately constrained to only work when it is safe:
This overload only participates in overload resolution if std::is_integral_v<IntegerType> is true.
If the T was not actually an integer type, rather than potentially having undefined behavior, the code won't compile. If the some_byte was not actually a std::byte, rather than potentially having undefined behavior, the code won't compile.
Beyond the expression of intent and safety issues already mentioned, I get the idea from the committee discussion on the paper that it’s meant to be like std::to_string and might have more overloads in the future.
A C style cast is not equivalent to std::to_integer<T>. See the below example.
std::to_integer<T> only participates in overload resolution if std::is_integral_v<T> is true.
#include <cstddef>
#include <iostream>
template <typename T>
auto only_return_int_type_foo(std::byte& b)
{
return std::to_integer<T>(b);
}
template <typename T>
auto only_return_int_type_bar(std::byte& b)
{
return T(b);
}
int main()
{
std::byte test{64};
// compiles
std::cout << only_return_int_type_foo<int>(test) << std::endl;
// compiler error
std::cout << only_return_int_type_foo<float>(test) << std::endl;
// compiles
std::cout << only_return_int_type_bar<int>(test) << std::endl;
// compiles
std::cout << only_return_int_type_bar<float>(test) << std::endl;
}
The following code:
int main() {
int a, b, c, d, e, f, g;
auto func = [&](){cout << a << b << c << d << e << f << g << endl;};
cout << sizeof(func) << endl;
return 0;
}
outputs 56 compiled with g++ 4.8.2
Since all local variables are stored in the same stack frame, remembering one pointer is sufficient to locate the addresses of all local variables. Why the lambda expression constructs a so big unnamed function object?
I do not understand why you seem surprised.
The C++ Standard gives a set of requirements, and every single implementation is free to pick any strategy that meets the requirements.
Why would an implementation optimize the size of the lambda object ?
Specifically, do you realize how that would tie down the generated code of this lambda to the generated code for the surrounding function ?
It's easy to say Hey! This could be optimized!, but it's much more difficult to actually optimize and make sure it works in all edge cases. So, personally, I much prefer having a simple and working implementation than a botched attempt at optimizing it...
... especially when the work-around is so easy:
struct S { int a, b, c, d, e, f, g; };
int main() {
S s = {};
auto func = [&](){
std::cout << s.a << s.b << s.c << s.d << s.e << s.f << s.g << "\n";
};
std::cout << sizeof(func) << "\n";
return 0;
}
Look Ma: 4 bytes only!
It is legal for a compiler to capture by reference via stack pointer. There is a slight downside (in that offsets have to be added to said stack pointer).
Under the current C++ standard with defects included, you also have to capture reference variables by pseudo-pointer, as the lifetime of the binding must last as long as the referred-to-data, not the reference it directly binds to.
The simpler implementation, where each captured variable corresponds to a constructor argument and class member variable, has the serious advantage that it lines up with "more normal" C++ code. Some work for magic this need be done, but other than that the lambda closure is a bog-standard object instance with an inline operator(). Optimization strategies on "more normal" C++ code will work, bugs are going to be mostly in common with "more normal" code, etc.
Had the compiler writers gone with the stack-frame implementation, probably reference capture of references in that implementation would have failed to work like it did in every other compiler. When the defect was resolved (in favor of it working), the code would have to be changed again. In essence, the compilers that would have used a simpler implementation would almost certainly have had fewer bugs and more working code than those who used a fancy implementation.
With the stack-frame capture, all optimization for a lambda would have to be customized for that lambda. It would be equivalent to a class that captured a void*, does pointer arithmetic on it, and casts the resulting data to typed pointers. That is going to be extremely hard to optimize, as pointer arithmetic tends to block optimization, especially pointer arithmetic between stack variables (which is usually undefined). What is worse is that such pointer arithmetic means that the optimization of stack variable state (eliminating variables, overlapping lifetime, registers) now has to interact with the optimization of lambdas in entangled ways.
Working on such an optimization would be a good thing. As a bonus, because lambda types are tied to compilation units, messing with the implementation of a lambda will not break binary compatibility between compilation units. So you can do such changes relatively safely, once they are a proven stable improvement. However, if you do implement that optimization, you really really will want the ability to revert to the simpler proven one.
I encourage you to provide patches to your favorite open-source compiler to add this functionality.
Because that's how it's implemented. I don't know if the standard says anything about how it should be implemented but I guess it's implementation defined how big a lambda object will be in that situation.
There would be nothing wrong for a compiler to store a single pointer and use the offsets, to do what you suggest, as an optimization. Perhaps some compilers do that, I don't know.
In the following line of code:
bootrec_reset(File(path, size, off), blksize);
Calling a function with prototype:
static void bootrec_reset(File &file, ssize_t blksize);
I receive this error:
libcpfs/mkfs.cc:99:53: error: invalid initialization of non-const reference of type 'File&' from an rvalue of type 'File'
libcpfs/mkfs.cc:30:13: error: in passing argument 1 of 'void bootrec_reset(File&, ssize_t)'
I'm aware that you can not pass non-const references (const &) to rvalues according to the standard. MSVC however allows you to do this (see this question). This question attempts to explain why but the answer makes no sense as he is using references to literals, which are a corner case and should obviously be disallowed.
In the given example it's clear to see that following order of events will occur (as it does in MSVC):
File's constructor will be called.
A reference to the File, and blksize, are pushed on the stack.
bootrec_reset makes use of file.
After returning from bootrec_reset, the temporary File is destroyed.
It's necessary to point out that the File reference needs to be non-const, as it's a temporary handle to a file, on which non-const methods are invoked. Furthermore I don't want to pass the File's constructor arguments to bootrec_reset to be constructed there, nor do I see any reason to manually construct and destroy a File object in the caller.
So my questions are:
What justifies the C++ standard disallowing non-const references in this manner?
How can I force GCC to permit this code?
Does the upcoming C++0x standard change this in anyway, or is there something the new standard gives me that is more appropriate here, for example all that jibberish about rvalue references?
Yes, the fact that plain functions cannot bind non-const references to temporaries -- but methods can -- has always bugged me. TTBOMK the rationale goes something like this (sourced from this comp.lang.c++.moderated thread):
Suppose you have:
void inc( long &x ) { ++x; }
void test() {
int y = 0;
inc( y );
std::cout << y;
}
If you allowed the long &x parameter of inc() to bind to a temporary long copy made from y, this code obviously wouldn't do what you expect -- the compiler would just silently produce code that leaves y unchanged. Apparently this was a common source of bugs in the early C++ days.
Had I designed C++, my preference would have been to allow non-const references to bind to temporaries, but to forbid automatic conversions from lvalues to temporaries when binding to references. But who knows, that might well have opened up a different can of worms...
"What justifies the C++ standard disallowing non-const references in this manner?"
Practical experience with the opposite convention, which was how things worked originally. C++ is to a large degree an evolved language, not a designed one. Largely, the rules that are still there are those that turned out to work (although some BIG exceptions to that occurred with the 1998 standardization, e.g. the infamous export, where the committee invented rather than standardizing existing practice).
For the binding rule one had not only the experience in C++, but also similar experience with other languages such as Fortran.
As #j_random_hacker notes in his answer (which as I wrote this was scored 0, showing that the scoring in SO really doesn't work as a measure of quality), the most serious problems have to do with implicit conversions and overload resolution.
"How can I force GCC to permit this code?"
You can't.
Instead of ...
bootrec_reset(File(path, size, off), blksize);
... write ...
File f(path, size, off);
bootrec_reset(f, blksize);
Or define an appropriate overload of bootrec_reset. Or, if "clever" code appeals, you can in principle write bootrec_reset(tempref(File(path, size, off)), blksize);, where you simply define tempref to return its argument reference appropriately const-casted. But even though that's a technical solution, don't.
"Does the upcoming C++0x standard change this in anyway, or is there something the new standard gives me that is more appropriate here, for example all that jibberish about rvalue references?"
Nope, nothing that changes things for the given code.
If you're willing to rewrite, however, then you can use e.g. C++0x rvalue references, or the C++98 workarounds shown above.
Cheers & hth.,
Does the upcoming C++0x standard change this in anyway, or is there something the new standard gives me that is more appropriate here, for example all that jibberish about rvalue references?
Yes. Since every name is an lvalue, it is almost trivial to treat any expression as if it was an lvalue:
template <typename T>
T& as_lvalue(T&& x)
{
return x;
}
// ...
bootrec_reset(as_lvalue(File(path, size, off)), blksize);
Is a fairly arbitrary decision - non-const references to temporaries are allowed when the temporary is the subject of a method call, for example (e.g. the "swap trick" to free the memory allocated by a vector, std::vector<type>().swap(some_vector);)
Short of giving the temporary a name, I don't think you can.
As far as I'm aware this rule exists in C++0x too (for regular references), but rvalue references specifically exist so you can bind references to temporaries - so changing bootrec_reset to take a File && should make the code legal.
Please note that calling C++0x "jibberish" is not presenting a very favorable picture of your coding ability or desire to understand the language.
1) Is actually not so arbitrary. Allowing non-const references to bind to r-values leads to extremely confusing code. I recently filed a bug against MSVC which relates to this, where the non-standard behavior caused standard-compliant code to fail to compile and/or compile with a deviant behavior.
In your case, consider:
#include <iostream>
template<typename T>
void func(T& t)
{
int& r = t;
++r;
}
int main(void)
{
int i = 4;
long n = 5;
const int& r = n;
const int ci = 6;
const long cn = 7;
//int& r1 = ci;
//int& r2 = cn;
func(i);
//func(n);
std::cout << r << std::endl;
}
Which of the commented lines to you want to compile? Do you want func(i) to change its argument and func(n) to NOT do so?
2) You can't make that code compile. You don't want to have that code. A future version of MSVC is probably going to remove the non-standard extension and fail to compile that code. Instead, use a local variable. You can always use a extra pair of braces to control the lifetime of that local variable and cause it to be destroyed prior to the next line of code, just like the temporary would be. Or r-value references.
{
File ftemp(path, size, off);
bootrec_reset(ftemp, blksize);
}
3) Yes, you can use C++0x r-value references in this scenario.
Alternatively, simply overload.
static void bootrec_reset(File &&file, ssize_t blksize) {
return bootrec_reset(file, blksize);
}
This is the easiest solution.
How can I force GCC to permit this code?
If you own the definition of File then you can try playing tricks such as this one:
class File /* ... */ {
public:
File* operator&() { return this; }
/* ... */
};
/* ... */
bootrec_reset(*&File(path, size, off), blksize);
This compiles for me in c++98 mode.
Does the upcoming C++0x standard change this in anyway, or is there something the new standard gives me that is more appropriate here, for example all that jibberish about rvalue references?
Obviously this the way to go if at all possible.