The latest proposal for a standard variant is P0088R0, found at http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2015/p0088r0.pdf
I have now implemented a superset of the proposal, which can be found at the git repository, https://github.com/jarro2783/thenewcpp/. The two extra features are support for recursive types, and the visitor takes arbitrary extra arguments.
The variant requires C++14, which has made the implementation significantly easier compared to C++11. Return type deduction for functions has meant that I can drop result_type
for visitors and I don’t need to rely on complex or redundant uses of decltype
.
Read more…
Recently, a proposal was made for a standards compliant variant, which can be found at http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4450.pdf. Apparently much has been said about what such a beast would look like, since there are many options for how to handle the edge cases. Nevertheless, a proposal has been made, and here are its key features:
- It can be empty.
- No restrictions on the types that can be used with regards to no throw move assignable etc.
- Provides relational operators which can be used as long as each type
T
providesT op T
, and uses the ordering in the type list if they are not the same type. - The return type of visitation can be automatically deduced.
- All memory is inside the variant object, i.e., it uses no extra heap allocation.
Since my variant is almost there, I have started the work needed to make my variant fit the standard. It will end up being a superset of the standard since I support generic multi-visitation with arbitrary arguments (more on that in another post), and I have a mechanism for recursive variants.
To make my variant not empty there are only a few minor things to do:
- If the index is
-1
on visitation, call the visitor’soperator()()
- Handle errors so that the index is set to
-1
appropriately, and the existing object is destructed.
Otherwise my variant already works.
In starting this, I have noticed that index
is a size_t
, but that it can also be -1
when empty. I’m not sure if this is an error, or if they intended it to mean the largest number that a size_t
can hold.
My last post about why you might not want to use auto may have left some people thinking that I think you shouldn’t use it. In fact I think you should almost always use it, as suggested in http://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/ However, like everything in C++, you should know what you are doing, because if you don’t, when you shoot yourself in the foot, you will probably blow your whole leg off.
You should read the above article, because it is a comprehensive discussion of why you might or might not want to use auto, and why you should use it all the time. Here I will add some points to the discussion about using it so that you don’t get yourself into trouble.
So now, I will go through every point in my last post, and then some.
According to https://isocpp.org/blog/2014/08/we-have-cpp14, the C++14 standard has been approved, and the official document is being finalised.
C++14 has some interesting new features, and relaxes some of the restrictions to the structures introduced by C++11, these include:
- templated variables
auto
in lambda functions- less restrictions on the use of
constexpr
- binary literals
So the auto
keyword is wonderful isn’t it? Maybe, maybe not. Whilst it can reduce the clutter when dealing with long type names, especially with templates, it has a few disadvantages.
const
iterators
Suppose that you have a non-const reference to a container that you wish to iterate through, and not change. You might do something like:
f(Container& c) { auto iter = c.begin(); while (iter != c.end()) { //do something here ++iter; } }
This would be wrong, because what you want is a constant iterator into the container, since you are not changing it. There are two ways to fix it, by using a for each loop or cbegin
and cend
. With a for each loop you get a reference to the item.
for (const auto& v : c) { //do something here }
when you actually want the iterators, you can use auto iter = c.cbegin()
.
A feature that I had been waiting a long time for is the ability to delegate constructors. To delegate a constructor means that one constructor can call another to initialise the object. When member object initialisation needed to be shared between constructors, or one constructor was the same as another but with some default arguments, the only way to do this was to either duplicate code or call some common function. Both have their problems, the first being that duplicated code is usually bad, the second being that you throw out any initialisation list advantages that you might get by calling some common function in the body of the constructor. Delegating constructors fixes this.
The syntax is quite simple, you simply call the constructor as the only item in an initialiser list. For example, the default constructor delegates to the integer constructor with the argument 42.
class C { public: C(int) { } C() : C(42) { } };
The standard specifies that if a constructor delegates to itself, the program is ill-formed. It also states that in that case no diagnostic is required. Given this example right out of the standard
class C { public: C(int) { } C() : C(42) { } C(char) : C(42.0) { } C(double) : C('a') { } }; int main() { C c('b'); return 0; }
Clang complains with the following error
delegate.cpp:8:15: error: constructor for 'C' creates a delegation cycle [-Wdelegating-ctor-cycles] C(double) : C('a') { } ^ delegate.cpp:7:3: note: it delegates to C(char) : C(42.0) { } ^ delegate.cpp:8:3: note: which delegates to C(double) : C('a') { } ^ 1 error generated.
GCC issues no such error, and compiles. The resulting executable crashes with a stack overflow. So be careful when you delegate constructors that you don’t create an infinite loop, because your compiler might not pick it up.
The addition of variadic templates allows an arbitrary-types tuple class to be added to C++. The tuple is essentially an unnamed struct with positional arguments — its elements are accessed through their position rather than by a name. It is a generalised version of the pair
class.
Firstly, it is defined in the header <tuple>
. To define a tuple, simply use std::tuple
with template arguments being the types that you want to be contained in the tuple.
For example, to declare a tuple whose three fields are char
, int
and string
, you would write
typedef std::tuple<char, int, std::string> MyTuple;
Code that I post here is available on the github repository https://github.com/jarro2783/thenewcpp
You can clone it with
git clone https://github.com/jarro2783/thenewcpp.git
There are standalone examples and actual code like the Variant. Any actual code is in the directory “juice”, and in the namespace Juice
. Any new code I make will be added to the namespace Juice
to build up the Juice C++11 project. It’s a play on Boost and the Boost Juice brand, and the project’s tagline is Juice up your C++11.