# So... C++14 is done :O

## Recommended Posts

Ohforf sake    2052
And there I haven't even given every C++11 feature a spin yet.

IMHO, you missed the best "new" feature in that list:
- Binary literals (yay, finally!)

##### Share on other sites
Ravyne    14300

You also missed extended constexpr Nevermind, no you didn't, but regardless -- for a complete rundown, see https://isocpp.org/wiki/faq/cpp14-language

The new auto return type is more than just extending it to functions, it also has more capability with respect to multiple return statements and recursive calls -- basically, as long as the first return statement's type can be deduced, you can now have subsequent return statements, including recursive calls, as long as they all agree on the type.

decltype(auto) is a new refinement of type deduction that is mostly useful to preserve the reference-ness where simple auto wouldn't -- My understanding is that it performs the same function as auto-with-trailing-return-type-of-decltype, but without the trailing part. It can be used anywhere a declaration can be used, which might be useful but early guidance is that this would be an anti-pattern. This is super important, but mostly for library-implementors.

There's also a new standard [[deprecated]] attribute to mark deprecated APIs, and you can now use the single quote (') character anywhere in numeric literals to separate digits to make them more readable, in any of the literal formats -- e.g. 0b0110'0011 to separate out the nybles in a byte.

Edited by Ravyne

##### Share on other sites
ongamex92    3256
Any hope/wish/desire for the next standard?

Compile time reflections (and being able to process them)

Edited by imoogiBG

##### Share on other sites
SeanMiddleditch    17565
Keep in mind that C++ also now has Technical Specifications, which are extensions to the language or library that are standardized in functionality but not required. e.g., Reflection is likely to land in a TS, meaning that any compiler that implements it will implement it in a compatible way but compilers can still be C++17 compliant without supporting it. Think of them sort of like OpenGL extensions, but each with the clear intent to roll into the proper standard once they've been hammered on by the community and unforeseen design mistakes are sorted out.

##### Share on other sites
KaiserJohan    2317

I still think the range-based loops were unnecessary.

Can you elaborate more? Personally I feel it is alot more readable.

##### Share on other sites
Chris_F    3030

Glad to see C++14 finalized. I've already been making use of it as I tend to use GCC and increasingly Clang/LLVM.

Concepts are nice, but what I think C++ desperately needs is modules, and I don't think it can wait till C++17. We need modules yesterday. Besides that, I personally would like to see a standardized ABI for C++, as purposed by N4028, as that is something which I personally think is important.

##### Share on other sites
Chris_F    3030

It is syntactic sugar, it is language bloat.

It can make code shorter and easier to read. C++ is already extremely large and complicated, removing the "bloat" of range-based for loops would not improve the situation in any noticeable way.

##### Share on other sites
Juliean    7077

Range based loops, however, added nothing. The old method was still there, and it is still there. The result is that instead of the traditional 'minimalist' approach, there is duplication. It did not enable any new thing. It did not add a feature. It did not do something that could not be trivially done before. It obscures details, adds an additional interface, but doesn't add any functionality. It is syntactic sugar, it is language bloat.

Range-based for loops basically implemented for-each loops, which in my book is good enough to warrant it. In my personal opinion, I totally love them. Sure, you could possibly do the same thing with a macro before, but its much cleaner that way:

std::vector<Class*> vValues;

for(auto pValue : vValues)
{
pValue->DoSomething();
}

for(auto itr = vValues.begin(); itr = vValues.end(); ++itr)
{
(*itr)->DoSomething(); // i totally loved having to do that itr-dereferencing as well before on vector<pointer>
}

I see your argument about not implementing something totally new, but fail to see why this is a bad thing. Everything that allows you to write code faster and cleaner at the same time, while not being forced upon, is IMHO a decent thing. Why would anyone ever prefer to write the loop the second way (and thats the nice version already using "auto")? Unless you want to delete something from the loop, yeah, quess that counts.

Edited by Juliean

##### Share on other sites
_the_phantom_    11250
Why would you write the loop the 2nd way when you have std::for_each and lambda functions?

##### Share on other sites
Juliean    7077

Why would you write the loop the 2nd way when you have std::for_each and lambda functions?

You don't,  unless you don't know about std::for_each like I did.

std::vector<Class*> vValues;

std::for_each(vValues.begin(), vValues.end(), [] (Class* pValue)
{
pValue->DoSomething();
});


I quess that works, especially if you make a wrapper function that removes the need for always specifiying "begin/end" when all you want is the full range. Quess I still prefer range-based loops, just a little more explicit than this version. (I do have to admit that this changes things a little bit at least, always more to learn :D).

##### Share on other sites
_the_phantom_    11250
C++14 would frame that more like;
std::for_each(std::begin(Values), std::end(Values), [](auto value)
{
value->DoAThing();
});

and once you get use to reading that then there is always this fun setup...

std::erase(std::remove_if(std::begin(values), std::end(values), [](auto value) { return !value->alive();}), std::end(values));

Edited by phantom

##### Share on other sites
satanir    1452

nullptr is just about the only feature of C++11 I actually use (though I was fine with the plain-old NULL). Ctr delegation is also nice, but again - syntactic sugar.

Seems to me like C++11/14 features are mostly beneficial if you are writing an STL like library (See Bjorn's FAQ, a lot of the examples are STL related).

I agree with frob - C++11 and C++14 are moving away from the minimalist approach, which isn't a good thing (and don't get me started on the dangers of 'auto').

##### Share on other sites
Ryan_001    3476

I find many of the C++11 features 'tacked' on and very hackish as is, so I'm actually dreading C++14 a bit.

##### Share on other sites
Juliean    7077

Why would you use any of those examples when the idiomatic version from the past 2 or 3 decades still works, is understood by everyone, and generates the same ASM?

Lots of things from the past still works, which still doesn't mean its the most optimal way of doing things. One can still send letters for written communication, but using online services is just a tad bit quicker ;)

Also while probably most people understand the old version, doesn't mean its easy to read. In fact, let alone the different variants, like having to explicitely store the end-condition and post/pre-increment validates a fixed solution for me.

More prone to error comes to mind, too. Especially for nested loops. Have lost count of how many times I mistakenly incremented the wrong variable or some stuff like that.

And as I already mentioned: Less code to type each time around - for the cost of what? That someone who hasn't seen this kind of loop might have to get used to reading it? From my personal view, the range-based loop is way easier to read, especially if you are new to the language - would anyone want to argue that a beginner would have an easier time with the old explicit loop?

Although generally, based on the old/new discussion, the question shouldn't be "what do I gain by using the new way" but rather "what do i lose by doing so?". People tend to favor that which they are used to, so unless there is a huge loss and virtually no gain for doing it with a new option, I'd say pick the new one.

EDIT:

nullptr is just about the only feature of C++11 I actually use (though I was fine with the plain-old NULL). Ctr delegation is also nice, but again - syntactic sugar.

Varadic templates are also very useful. Reduces code complexity on things like signal/slots, delegates etc... by a drastic amount, also allowing for perfect forwarding.

Edited by Juliean

##### Share on other sites
satanir    1452

Although generally, based on the old/new discussion, the question shouldn't be "what do I gain by using the new way" but rather "what do i lose by doing so?". People tend to flavor that which they are used to, so unless there is a huge loss and virtually no gain for doing it with a new option, I'd say pick the new one.

I actually think the question is "in how many ways can someone else screw-up the code?". And in C++11 the answer is a lot more then with the "old" C++.

If you are an experienced programmer, who understands the pros and cons of the new feature - then use whatever you want. The problem is that there are way too many inexperienced programmers who (unintentional?) abuse things, or will understand what you are doing.

Take 'auto' for example - it's great when used with caution (and very very sparsely). But I saw some programmers who just decided that it would be great to use it as much as possible (even a very experienced one), regardless of minor things like code readability and type safety.

[EDIT] - 'type safety' should be 'compile time type checks'.

C++ is not a scripting language, nor is it a high-level abstracted language with powerful RTTI like Java. And it shouldn't be. Most of the new features seem to solve very minor problems and do not provide great benefit over the legacy C++.

Edited by N.I.B.

##### Share on other sites
alvaro    21266
Regarding the different ways of writing a loop that iterates through a vector, I prefer using indices most of the time, because it's the easiest to modify in the future.

for (size_t i = 0, end = vValues.size(); i != end; ++i)
vValues[i]->DoSomething();

What if I only want to DoSomething on elements that pass some test?
for (size_t i = 0, end = vValues.size(); i != end; ++i) {
if (some_test(*vValues[i]))
vValues[i]->DoSomething();
}

What if I need to add more elements to the vector in some circumstances?
for (size_t i = 0; i != vValues.size(); ++i) {
vValues[i]->DoSomething();
if (some_circumstances(*vValues[i]))
vValues.push_back(some_new_thing); // Iterators are now invalid, but indices still work just fine
}

Try to do that with any of the other styles.

For very simple loops, the range-based format is very clean and I sometimes use it. Edited by Álvaro

##### Share on other sites
Any hope/wish/desire for the next standard?

Compile time reflections (and being able to process them)

Or, at least introspection (I can see how reflection may be harsh on the implementator side, but introspection is just about accessing something the compiler already knows anyway). I would already be totally happy with that.

Most importantly, I'd like being able to ask an enumeration for the number of elements/members inside it, in a straightforward, no-hack, robust way (robust insofar as "holes" in enumeration values don't give you a wrong count as the typical "workaround" does). But of course there are plenty of other things that you may wish to query, which the compiler readily knows (and which don't involve complex program-rewriting voodoo on the implementor side).

I'd rather like to see this than things like networking or filesystem stuff, which are usually "nasty low level stuff" anyway unless you only want to do the most basic things. On the other hand, they're hard to get right and wrap into one standard (unless you leave out most of it, or you do like the proposal does "implementation does not need to support blah blah if the system does not") and they're things that constantly change. Some filesystem support hard links and some don't. Some support long names, attributes, alternate streams, and terabyte-sized files, and transactions, others don't. Some have a single root, others don't. Maybe in a few years from now the hierarchy will look radically different from anything we have now, who can tell?

Once upon a time, 20 years ago, BeOS had no filesystem at all, but merely a full-disk relational database (which would also store the "files"). Granted, that was a failure, but for economic reasons, not because it didn't work well -- it certainly worked, and it was darn cool. It might very well be that 10 or 20 years from now, all filesystems look like this, it only takes someone to pick up the old idea and produce a working, performant implementation on a mainstream system. Then what would I do with my C++ filesystem code?

##### Share on other sites
Juliean    7077

Take 'auto' for example - it's great when used with caution (and very very sparsely). But I saw some programmers who just decided that it would be great to use it as much as possible (even a very experienced one), regardless of minor things like code readability and type safety.

I'm kind of uncertain about 'auto', as it can truly screw over beginners, but I also think that it can in fact even increase readability. E.g:

void Saver::SaveMethod(xml::Node& node, const ExecutionUnit& unit, bool isMain)
{
xml::Node* pTriggersNode = nullptr;
if(isMain)
pTriggersNode = &node.InsertNode(L"Trigger");

auto& commandNode = node.InsertNode(L"Commands");

auto& mEvents = unit.GetEvents();
if(!mEvents.empty())
{
std::map<unsigned int, const Command*> mSorted;
for(auto& event : mEvents)
{
mSorted.emplace(event.second->GetId(), event.second);
}

// ...
}
}


Does it really matter for the reader which type "commandNode"  and "mEvents" is? The names & assignments already clearly state which purpose this variable has, and whether its an xml::Node or an xml::WriteNode or so don't really help on that. For that reason, I would even go as far as to say that in this case it makes it more readable.

Type-safety is always there with auto, unless you are talking about a special case, its just that the type is "invisible" and will adjust to what is assigned to the variable, but compile-time type checks still apply.

What if I only want to DoSomething on elements that pass some test?

for (size_t i = 0, end = vValues.size(); i != end; ++i) {
if (some_test(*vValues[i]))
vValues[i]->DoSomething();
}

You can do that with the range-based loop too.

for(auto pValue : vValues)
{
if(some_test(pValue))
pValue->DoSomething)
}


Loop-modification is really the only perfect reason where range-based loops totally don't work.

Edited by Juliean

##### Share on other sites

Take 'auto' for example - it's great when used with caution (and very very sparsely). But I saw some programmers who just decided that it would be great to use it as much as possible (even a very experienced one), regardless of minor things like code readability and type safety.

One of those programmers who decided that using auto as much as possible is Herb Sutter. It is a misconception that auto makes your code worse or unsafe in any way.

It's not "just any type" like in some scripting languages, not at all. auto requires that the type is exactly known, and it results in a precisely defined type (which you don't see in your code, but the compiler knows it!) that is properly checked as if you typed it out by hand. It has hardly any disadvantages (if any), but lots of advantages. And those are not only that it's less work to type auto than to type std::foo<bar<baz, bat>>::blah::iterator when you actually don't care about what the type is exactly, as long as it's correct.

##### Share on other sites
Alessio1989    4634
A lot of people use post-increment in loops without any reason, so for-range loops are welcomed since they (should) use only the pre-increment. Oh yes, you also type less, and they fits well with STL containers.

The auto type is also lovely when you are assigning the vale from a C++ cast (especially if the cast is applied to a byte array). Since I tend to use verbose names, using auto removes me a lot of typing, especially when I need to refactoring. It is also sweet in templates °_° Edited by Alessio1989

##### Share on other sites
alvaro    21266

You can do that with the range-based loop too.

for(auto pValue : vValues)
{
if(some_test(pValue))
pValue->DoSomething)
}

Loop-modification is really the only perfect reason where range-based loops totally don't work.

Here's another one:
for (size_t i = 0, end = vValues.size(); i != end; ++i) {
if (i > 0)
std::cout << ", ";
std::cout << *vValues[i];
}
std::cout << '\n';