STRICT_VARIANT A simpler variant in C++ Chris Beck

Slides:



Advertisements
Similar presentations
Chapter 18 Vectors and Arrays
Advertisements

Chapter 18 Vectors and Arrays John Keyser’s Modification of Slides by Bjarne Stroustrup
What's new in Microsoft Visual C Preview
Chapter 17 vector and Free Store John Keyser’s Modifications of Slides By Bjarne Stroustrup
Informática II Prof. Dr. Gustavo Patiño MJ
CS 4800 By Brandon Andrews.  Specifications  Goals  Applications  Design Steps  Testing.
ARRAYS AND POINTERS Although pointer types are not integer types, some integer arithmetic operators can be applied to pointers. The affect of this arithmetic.
1 Pointers, Dynamic Data, and Reference Types Review on Pointers Reference Variables Dynamic Memory Allocation –The new operator –The delete operator –Dynamic.
1 Procedural Concept The main program coordinates calls to procedures and hands over appropriate data as parameters.
The Rest of the Story.  Constructors  Compiler-generated  The Initializer List  Copy Constructors  Single-arg (conversion ctors)  The Assignment.
Programming Language C++ Xulong Peng CSC415 Programming Languages.
Addresses in Memory When a variable is declared, enough memory to hold a value of that type is allocated for it at an unused memory location. This is.
Netprog: Java Intro1 Crash Course in Java. Netprog: Java Intro2 Why Java? Network Programming in Java is very different than in C/C++ –much more language.
Computer Science and Software Engineering University of Wisconsin - Platteville 2. Pointer Yan Shi CS/SE2630 Lecture Notes.
Copyright  Hannu Laine C++-programming Part 1 Hannu Laine.
C++ Memory Overview 4 major memory segments Key differences from Java
C++ Data Types Structured array struct union class Address pointer reference Simple IntegralFloating char short int long enum float double long double.
Copyright 2005, The Ohio State University 1 Pointers, Dynamic Data, and Reference Types Review on Pointers Reference Variables Dynamic Memory Allocation.
C# Classes and Inheritance CNS 3260 C#.NET Software Development.
Inheritance Initialization & Destruction of Derived Objects Protected Members Non-public Inheritance Virtual Function Implementation Virtual Destructors.
1 Recall that... char str [ 8 ]; str is the base address of the array. We say str is a pointer because its value is an address. It is a pointer constant.
1 Chapter 15-1 Pointers, Dynamic Data, and Reference Types Dale/Weems.
CHAPTER 18 C – C++ Section 1: Exceptions. Error Handling with Exceptions Forces you to defend yourself Separates error handling code from the source.
CS314 – Section 5 Recitation 9
Memory Management.
Jim Fawcett CSE687-OnLine – Object Oriented Design Summer 2017
The C++ Data Types Fundamental Data Types
Jim Fawcett CSE687 – Object Oriented Design Spring 2001
Programming Languages and Compilers (CS 421)
CSE341: Programming Languages Lecture 11 Type Inference
Exceptions David Rabinowitz.
Overview 4 major memory segments Key differences from Java stack
How to be generic Lecture 10
Jim Fawcett CSE687 – Object Oriented Design Spring 2015
Type Checking Generalizes the concept of operands and operators to include subprograms and assignments Type checking is the activity of ensuring that the.
Chapter 13: Pointers, Classes, Virtual Functions, and Abstract Classes
OOP-4-Templates, ListType
Review: Two Programming Paradigms
C++ in 90 minutes.
C++ Memory Management Idioms
Chapter 1-4 CSc 212 Data Structures, Sec AB CCNY, Spring 2012
Chapter 10: Pointers Starting Out with C++ Early Objects Ninth Edition
This pointer, Dynamic memory allocation, Constructors and Destructor
Pointers and Dynamic Variables
Linked Lists.
Overview 4 major memory segments Key differences from Java stack
Chapter 15 Pointers, Dynamic Data, and Reference Types
Pointers, Dynamic Data, and Reference Types
CMSC 202 Lesson 22 Templates I.
Chapter 15 Pointers, Dynamic Data, and Reference Types
CSE341: Programming Languages Lecture 11 Type Inference
CSCE 489- Problem Solving Programming Strategies Spring 2018
Variables, Identifiers, Assignments, Input/Output
Prof. Bhushan Trivedi Director GLS Institute of Computer Technology
CSE341: Programming Languages Lecture 11 Type Inference
Objects Managing a Resource
Templates I CMSC 202.
Java Programming Language
CSE341: Programming Languages Lecture 11 Type Inference
Dynamic allocation (continued)
Data Structures and Algorithms Memory allocation and Dynamic Array
Lists CMSC 202, Version 4/02.
EECE.3220 Data Structures Instructor: Dr. Michael Geiger Spring 2019
CSE341: Programming Languages Lecture 11 Type Inference
Lists CMSC 202, Version 4/02.
Binding 10: Binding Programming C# © 2003 DevelopMentor, Inc.
Pointers, Dynamic Data, and Reference Types
Exceptions.
Copy Constructors and Overloaded Assignment
Chapter 1-4 CSc 212 Data Structures, Sec FG CCNY, 2009
Presentation transcript:

STRICT_VARIANT A simpler variant in C++ Chris Beck https://github.com/cbeck88/strict_variant

What is a variant? A variant is a heterogenous container. std::vector<T> many objects of one type std::variant<T, U, V> one object of any of T, U, or V AKA “tagged-union”, “typesafe union”

What is a union? struct bar { // Size is sum of sizes, short a; // plus padding for alignment float b; double c; }; union foo { // Size is max of sizes, short a; // alignment is max of alignments float b; double c; };

Storing to union may change the active member. union foo { short a; float b; double c; }; int main() { foo f; f.a = 5; f.a += 7; f.b = 5; f.b += .5f; } Storing to union may change the active member. Reading inactive member may lead to implementation-defined or undefined behavior!

Why would you use this? Need to store several types of objects in a collection, but no natural inheritance relation. Using an array of unions, store objects contiguously, with very little memory wasted. Low-level signals / event objects Messages matching various schema

struct SDL_KeyboardEvent { Uint32 type; // SDL_KEYDOWN or SDL_KEYUP Uint8 state; // SDL_PRESSED or SDL_RELEASED SDL_Keysym keysym; // Represents the key that was pressed }; struct SDL_MouseMotionEvent { Uint32 type; // SDL_MOUSEMOTION Uint32 state; // bitmask of the current button state Sint32 x; Sint32 y; union SDL_Event { SDL_KeyboardEvent key; SDL_MouseMotionEvent motion; ...

Why would you use this? A variant is a type-safe alternative to a union Prevents you from using inactive members Ensures that destructors are called when the active member changes – crucial for C++!

Query the active member using get: void print_variant(boost::variant<int, float, double> v) { if (const int * i = boost::get<int>(&v)) { std::cout << *i; } else if (const float * f = boost::get<float>(&v) { std::cout << *f; } else if (const double * d = boost::get<double>(&v) { std::cout << *d; } else { assert(false); } boost::get returns null if requested type doesn’t match run-time type.

Better, use a visitor: void print_double(double d) { std::cout << d; } void print_variant(boost::variant<int, float, double> v) { boost::apply_visitor(print_double, v); This only works because int, float can be promoted to double as part of overload resolution.

Using a lambda as a visitor (C++14): void print_variant(boost::variant<int, float, double> v) { boost::apply_visitor([](auto val) { std::cout << val; }, v); } No promotion here! More generally, use templates in the visitor object.

Recursive Data Structures (XML) struct mini_xml; using mini_xml_node = boost::variant<boost::recursive_wrapper<mini_xml>, std::string>; struct mini_xml { std::string name; std::vector<mini_xml_node> children; }; recursive_wrapper<T> is “syntactic sugar” It works like std::unique_ptr<T> But when visiting, or using get, can pretend it is T.

Pattern Matching (Rust): enum Message { Quit, ChangeColor(i32, i32, i32), Move { x: i32, y: i32 }, Write(String), } fn process_message(msg: Message) { match msg { Message::Quit => quit(), Message::ChangeColor(r, g, b) => change_color(r, g, b), Message::Move { x, y } => move_cursor(x, y), Message::Write(s) => println!("{}", s);

Pattern Matching (C++): using Message = boost::variant<Quit, ChangeColor, Move, Write>; void process_message(const Message & msg) { boost::apply_visitor( overload([](Quit) { quit(); }, [](ChangeColor c) { change_color(c.r, c.g, c.b); } [](Move m) { move_cursor(m.x, m.y); } [](Write w) { std::cout << w.s << std::endl; }), , msg); }

Existing Implementations boost::variant std::variant (C++17) strict_variant (this talk) and others... Surprisingly, many significant design differences and tradeoffs!

Problem: Exception Safety How to handle throwing, type-changing assignment. ~A() Now B(...) throws... Now what? A is already gone, and have no B B 1 A

Solution: Double Storage If B(...) throws, still have A ~A() When C comes, flip back to first side C B 5 1 A B

Solution: boost::variant First move A to heap. (If it fails, we are still ok.) If B(...) succeeds, delete A pointer. If B(...) fails, move A pointer to storage. (Can’t fail.) B 4 2 1 B A A*

Solution: std::variant ~A(), set counter to 0 Now B(...) throws... Now we are “empty”. valueless_by_exception() reports true visiting is an error until new value provided! B 1 A

Because of C++ language rules, we can’t have everything we want. Tradeoffs Because of C++ language rules, we can’t have everything we want. No wasted memory No empty state Strong exception-safety, rollback semantics No dynamic allocations, backup copies

Solution: strict_variant If B(B&&) can’t throw, great, do the obvious. If B(B&&) can throw, B always lives on heap. Construct B on heap. If it fails, didn’t touch A. ~A(), then move B* to storage. Can’t fail. B B 1 2 A B*

strict_variant design High level design: Reducing to a simpler problem. Make a “simple” variant which assumes members are nothrow moveable. (This is easy!) Then, to make a general variant, stick anything that throws in a recursive_wrapper and use the simple code. (Pointers can always be moved!)

“Step 2”, the reduction, fits here on the screen. template <typename T> struct wrap_if_throwing_move { using type = typename std::conditional< std::is_nothrow_move_constructible<T>::value, T, recursive_wrapper<T> >::type; }; using wrap_if_throwing_move_t = typename wrap_if_throwing_move<T>::type; template <typename... Ts> using variant = simple_variant<wrap_if_throwing_move_t<Ts>...>;

Why use strict_variant instead of boost::variant? boost::variant supports even C++98 This means, it has to basically work even if we can’t check noexcept status of operations. This greatly limits design options. strict_variant targets C++11 This allows an, IMO, simpler and better strategy.

Empty State Exception Safety Backup Copies Number of states double storage no yes 2n std::variant n+1 boost::variant strict_variant n

Other features boost::variant and std::variant sometimes do annoying things strict_variant uses SFINAE to prevent many “evil” standard conversions here. std::variant<int, std::string> v; v = true; // Compiles! Because of bool -> int :( std::variant<bool, std::string> u; u = "The future is now!"; // Selects bool, not std::string! :(

http://chrisbeck.co http://github.com/cbeck88/ THANK YOU http://chrisbeck.co http://github.com/cbeck88/