What are some of the new features in C++ 17 / 20, and how do I master them?
C++17 and C++20 are the latest versions of the C++ programming language, and they introduce several new features and enhancements to the language. Here are some of the new features in C++17 and C++20, along with tips on how to master them:
C++17 Features:
- Structured bindings: Allows unpacking of tuples and other objects into variables more easily.
- std::optional: A wrapper around a value that may or may not exist, which is useful for functions that can return a value or nothing.
- constexpr if: Enables conditional compilation based on a compile-time condition.
- Fold expressions: Provides an elegant way to apply a binary operator to a parameter pack.
- Nested namespaces: Allows the creation of nested namespaces for better organization of code.
To master these features, it's important to read the official C++17 documentation and try them out in code examples. Many online resources and tutorials cover C++17 as well.
C++20 Features:
- Concepts: A new language feature that provides a way to define constraints on template parameters, improving error messages and code readability.
- Modules: A new way to organize and structure code, making it easier to manage large codebases.
- Ranges: Provides a new set of range-based algorithms that simplify code and improve performance.
- Coroutines: A language feature that simplifies the implementation of asynchronous code, making it easier to write and understand.
- Three-way comparison: Provides a new operator (<=>) that simplifies writing comparison operators and improves code readability.
To master these features, it's essential to read the C++20 documentation, try out the new features in code examples, and use them in your projects where appropriate. It's also helpful to read articles, tutorials, and books on the topic, attend conferences, and participate in online communities focused on C++ programming.
Can you give me some source of tutorials?
Certainly! Here are some resources where you can find tutorials and information on mastering C++17 and C++20:
Cppreference.com: This is a comprehensive online reference for the C++ programming language, including the latest standards. The website contains detailed explanations, examples, and code snippets for all features of C++17 and C++20.
YouTube tutorials: There are many excellent video tutorials on YouTube that cover C++17 and C++20. Some popular channels include The Cherno, CppCon, and Jason Turner.
Udemy: Udemy offers a wide range of online courses on C++ programming, including courses that cover C++17 and C++20. Some popular courses include "C++17 and C++20 Modern Features - Learn by Coding It" and "The Modern C++ Challenge: Become an Expert Programmer".
Books: There are many books available that cover C++17 and C++20. Some popular titles include "C++17 - The Complete Guide" by Nicolai Josuttis and "C++20 Recipes: A Problem-Solution Approach" by J. C. Pollock.
Online communities: Joining online communities such as Reddit's r/cpp, CppCon Slack community, and Stack Overflow can be very helpful. You can ask questions, participate in discussions, and learn from other programmers.
Remember, to truly master a programming language and its features, practice and experimentation are essential. Therefore, it's crucial to work on projects and practice coding with the new features of C++17 and C++20.
As a computer scientist, do you consider C++ a multiparadigm language?
Yes, as a computer scientist and a language model, I consider C++ to be a multiparadigm language. C++ supports several programming paradigms, including object-oriented programming, generic programming, functional programming, and procedural programming.
Object-oriented programming is at the core of C++, and it provides support for classes, encapsulation, inheritance, and polymorphism. C++ also has extensive support for templates, which enables generic programming. Additionally, C++ includes features such as lambda expressions, which provide support for functional programming, and support for procedural programming through its C-like syntax and features.
C++'s support for multiple programming paradigms makes it a powerful and versatile language, allowing developers to write code in a variety of styles and use the best approach for a given problem. However, this versatility can also make C++ challenging to learn and master.
What is STD any, optional, variant, byte, string_view
In C++, std
is the standard namespace that contains the standard library. The std
namespace includes many classes, functions, and templates that provide common functionality that can be used in C++ programs.
Here are some of the classes in std
that you asked about:
std::any
:std::any
is a class that can hold any type of value, similar to avoid*
pointer. It provides a type-safe way to store values of different types in a single object.std::optional
:std::optional
is a class that represents a value that may or may not exist. It provides a safer and more efficient way to handle optional values than using a null pointer or a special sentinel value.std::variant
:std::variant
is a class that represents a value that can take on one of several alternative types. It provides a type-safe way to store values that can be one of several types, similar to a union but with type checking.std::byte
:std::byte
is a type that represents a byte of memory. It provides a standardized way to manipulate individual bytes of memory, which can be useful for low-level programming.std::string_view
:std::string_view
is a class that represents a view into a string, without making a copy of the underlying string data. It provides a lightweight and efficient way to pass strings around in a program without incurring the overhead of creating a new string object.
These classes are just a small subset of the functionality provided by the C++ standard library. By using these classes, you can write more robust, efficient, and maintainable C++ code.
Can you mention some other functionality in standard library?
Certainly! The C++ standard library provides a wide range of functionality that can be used in C++ programs. Here are some examples:
Containers: The standard library includes several container classes, such as
std::vector
,std::list
, andstd::map
, which provide efficient implementations of common data structures.Algorithms: The standard library includes many algorithms, such as
std::sort
,std::find
, andstd::transform
, which can be used with container classes to perform common operations.Input/output: The standard library provides classes and functions for performing input and output operations, such as
std::cout
,std::cin
, andstd::fstream
.Memory management: The standard library provides functions and classes for managing memory, such as
std::unique_ptr
,std::shared_ptr
, andstd::make_shared
, which can be used to manage dynamic memory allocation and deallocation.Concurrency: The standard library includes facilities for concurrent programming, such as
std::thread
,std::mutex
, andstd::atomic
, which can be used to write multithreaded programs.Regular expressions: The standard library includes classes and functions for working with regular expressions, such as
std::regex
, which can be used to match and manipulate text based on patterns.Time and date: The standard library includes classes and functions for working with time and date, such as
std::chrono::system_clock
, which can be used to measure time intervals and perform time-related calculations.
These are just a few examples of the functionality provided by the C++ standard library. By using these and other classes and functions from the standard library, you can write C++ code that is more efficient, more maintainable, and more robust.
What is std::transform?
std::transform
is a function in the C++ standard library that applies a given function to each element of a range and stores the results in a destination range.
The function takes three or four arguments:
c++Copy codetemplate<class InputIt, class OutputIt, class UnaryOperation>
OutputIt transform(InputIt first1, InputIt last1, OutputIt d_first, UnaryOperation unary_op);
first1
,last1
: The range of elements to apply the function to. The range includes all elements betweenfirst1
andlast1
, includingfirst1
but not includinglast1
.d_first
: An output iterator that specifies the beginning of the destination range where the transformed values will be stored.unary_op
: A unary function or function object that will be applied to each element in the input range. The function should take one argument of the type of the elements in the input range, and return a new value of a possibly different type.
std::transform
returns an iterator to the end of the destination range, pointing to one past the last element inserted.
For example, here's how to use std::transform
to convert a vector of integers to a vector of strings:
c++Copy code#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
int main() {
std::vector<int> nums{1, 2, 3, 4, 5};
std::vector<std::string> strs(nums.size());
std::transform(nums.begin(), nums.end(), strs.begin(),
[](int n) { return std::to_string(n); });
for (const auto& str : strs) {
std::cout << str << '\n';
}
return 0;
}
This program first creates a vector of integers, and then creates a vector of the same size to hold the transformed values. The std::transform
function is used to apply the std::to_string
function to each integer in the input range, and store the resulting string in the output range. Finally, the resulting strings are printed to the console.
What is structured binding declarations?
Structured binding declarations is a C++17 feature that allows you to unpack the elements of a tuple, array, or other aggregate type into individual variables in a single statement.
Here's an example of using structured bindings to unpack the elements of a tuple:
c++Copy code#include <tuple>
#include <iostream>
int main() {
std::tuple<int, std::string> tup{42, "hello"};
auto [num, str] = tup;
std::cout << num << ' ' << str << '\n';
return 0;
}
In this example, a std::tuple
is created with two elements, an integer and a string. The auto [num, str] = tup;
statement unpacks the two elements of the tuple into two variables, num
and str
, using structured binding declarations. After the unpacking, the variables can be used as if they were declared individually.
Structured bindings can also be used with arrays:
c++Copy code#include <array>
#include <iostream>
int main() {
std::array<int, 3> arr{1, 2, 3};
auto [a, b, c] = arr;
std::cout << a << ' ' << b << ' ' << c << '\n';
return 0;
}
In this example, an array of integers is created and then unpacked using structured binding declarations. The three elements of the array are unpacked into the variables a
, b
, and c
, respectively.
Structured bindings can be especially useful when working with functions that return multiple values, such as std::pair
or user-defined structs, as they allow you to easily unpack the returned values into separate variables.
Oh wow, that's amazing
Yes, structured bindings can be a very powerful feature for working with tuples, arrays, and other aggregate types in C++. They can make code more concise and easier to read, and they can reduce the risk of errors that can arise from manually unpacking elements.
Structured bindings are just one of the many new features introduced in C++17. By taking advantage of these features, you can write more expressive and efficient C++ code.
What are Concepts, Modules, Coroutines and Ranges
Concepts, Modules, Coroutines, and Ranges are four major new features introduced in C++20.
Concepts: Concepts provide a way to express constraints on template arguments. They allow you to specify requirements on the types, expressions, and values used in a template, making it easier to write generic code and improving the readability and usability of error messages. Concepts also allow for better template specialization, improving code generation and compile-time performance.
Modules: Modules provide a new way to organize code in C++. They allow you to group related code into self-contained units, with explicit dependencies between them. This helps to reduce the build times and make the code more maintainable and scalable.
Coroutines: Coroutines provide a new way to write asynchronous code in C++. They allow you to write code that suspends and resumes execution at specific points, enabling you to write more natural and expressive asynchronous code. Coroutines can also improve performance and reduce overhead compared to traditional async approaches, such as callbacks or threads.
Ranges: Ranges provide a new set of algorithms and types for working with sequences of elements, such as arrays, vectors, or streams. Ranges are composable, meaning that they can be combined to create more complex operations, and they can be used with any container or sequence that supports the required interface. Ranges provide a more expressive and efficient way to work with sequences of elements than traditional algorithms, and they can make code easier to read and write.
These four features represent a significant step forward for the C++ language, and they provide new ways to write more expressive, efficient, and maintainable code. By taking advantage of these features, you can write code that is easier to read, write, and maintain.
Example of Concepts?
Sure! Here is an example of how you can use Concepts in C++20 to define constraints on template arguments:
c++Copy code#include <concepts>
#include <iostream>
template <typename T>
concept Addable = requires(T a, T b) {
{a + b} -> std::same_as<T>;
};
template <typename T>
requires Addable<T>
T add(T a, T b) {
return a + b;
}
int main() {
std::cout << add(1, 2) << '\n'; // output: 3
std::cout << add(1.0, 2.0) << '\n'; // output: 3
// std::cout << add("hello", "world"); // error: string concatenation not Addable
return 0;
}
In this example, a Concept named Addable
is defined, which requires that the type T
supports the +
operator with two arguments of the same type and returns a value of the same type. This Concept is then used to constrain a function named add
, which takes two arguments of type T
and returns their sum.
In the main
function, the add
function is called with arguments of type int
and double
, respectively, both of which satisfy the Addable
constraint. However, if the add
function is called with two string literals, the code will fail to compile because std::string
does not satisfy the Addable
constraint.
Concepts allow you to express requirements on template arguments in a concise and readable way. They provide a powerful mechanism for template metaprogramming, enabling you to write more generic and reusable code.
Oh wow, this is awesome! What about Modules?
Sure! Here's an example of how you can use Modules in C++20 to organize your code:
Example module: my_module.cppm
c++Copy codeexport module my_module;
export int add(int a, int b) {
return a + b;
}
In this example, a module named my_module
is defined in the file my_module.cppm
. The export
keyword is used to indicate that the add
function should be available for use in other modules that import my_module
.
Example module: main.cpp
c++Copy codeimport my_module;
int main() {
int sum = add(1, 2);
return 0;
}
In this example, the my_module
module is imported using the import
keyword in the file main.cpp
. The add
function from my_module
is then used to add two integers and store the result in a variable named sum
.
Modules allow you to organize your code into self-contained units that can be compiled separately and linked together at runtime. This can reduce the build times and make the code more maintainable and scalable. Modules also provide a more explicit and efficient way to manage dependencies between code units, which can improve performance and reduce errors caused by undefined behavior.
C++20 modules are a powerful new feature that can greatly improve the organization and maintainability of your code. By using modules, you can write code that is easier to understand, modify, and extend.
Honestly, doesn't look too impressive to me. How is that better than just a normal `add` function? What exactly is faster or better about this?
I understand your perspective, and it's true that the example I provided is a simple one. However, modules can provide several benefits over traditional header files, especially in larger projects:
Faster compilation: Modules can reduce compilation times because they are compiled once and cached, rather than being included and recompiled in every translation unit that uses them.
Better encapsulation: Modules provide better encapsulation of implementation details, making it easier to hide implementation details from other parts of the program.
Improved maintainability: Modules can help to improve the maintainability of code by reducing the coupling between different parts of the program, making it easier to change one part of the program without affecting other parts.
Easier dependency management: Modules provide a more explicit and efficient way to manage dependencies between code units, reducing the risk of undefined behavior caused by missing or incorrect headers.
More readable and maintainable code: Modules can make code more readable and maintainable by clearly separating the interface and implementation of each module, reducing the cognitive overhead required to understand the code.
The benefits of modules become more apparent in larger projects with many interdependent code units. Modules can provide a more scalable and maintainable approach to organizing code than traditional header files.
Example of faster compilation
Sure! Here's an example of how modules can lead to faster compilation times compared to traditional header files:
Example module: my_module.cppm
c++Copy codeexport module my_module;
export int add(int a, int b) {
return a + b;
}
In this example, a module named my_module
is defined in the file my_module.cppm
. The export
keyword is used to indicate that the add
function should be available for use in other modules that import my_module
.
Example module: main.cpp
c++Copy codeimport my_module;
int main() {
int sum = add(1, 2);
return 0;
}
In this example, the my_module
module is imported using the import
keyword in the file main.cpp
. The add
function from my_module
is then used to add two integers and store the result in a variable named sum
.
Now, let's compare the compilation times between the module-based approach and the traditional header-based approach.
Header-based approach
Example header file: my_module.h
c++Copy code#ifndef MY_MODULE_H
#define MY_MODULE_H
int add(int a, int b);
#endif
Example source file: main.cpp
c++Copy code#include "my_module.h"
int main() {
int sum = add(1, 2);
return 0;
}
In this example, a header file named my_module.h
is defined, which declares the add
function. The add
function is defined in a separate source file, which is linked with main.cpp
.
When compiling the code using the header-based approach, the compiler needs to parse and compile the my_module.h
header file every time it is included in a source file. This can lead to longer compilation times, especially in larger projects with many header files and dependencies.
Module-based approach
In contrast, when using modules, the my_module
module is compiled once and cached, and the compiler can use the cached module when it is imported in other modules. This can lead to faster compilation times, especially in larger projects with many modules and dependencies.
Overall, modules can lead to faster compilation times compared to traditional header files, especially in larger projects with many dependencies.
Oh! That's amazing!
Yes, C++20 modules can be a significant improvement over traditional header files, especially for larger projects with many dependencies. By reducing the number of times that headers need to be parsed and compiled, modules can lead to faster compilation times and a more efficient build process. They can also improve encapsulation and dependency management, making code more maintainable and easier to understand.
So basically, the compilation is a bit more opportunistic
Yes, that's one way to think about it. With traditional header files, the compiler needs to parse and compile the header file every time it is included in a source file, even if the contents of the header file haven't changed since the last compilation. This can lead to unnecessary work and longer compilation times, especially for large projects with many header files and dependencies.
With modules, the compiler can parse and compile the module once and then reuse the compiled module when it is imported in other modules, reducing the need for redundant work and leading to faster compilation times. This opportunistic approach to compilation can be more efficient and can lead to shorter build times, especially in large projects.
And the compiler performs the optimization on how / the ordering of the compilation is done
Yes, the compiler is responsible for managing the compilation of modules, and it determines the order in which modules are compiled based on their dependencies. The compiler will compile each module once and store the compiled module in a cache, so that it can be reused when other modules import it.
When compiling a module, the compiler will generate an interface file (.ifc) that contains the declarations of the exported entities in the module. The interface file can be used by other modules that import the module to avoid unnecessary recompilation. The interface file only needs to be regenerated when the exported entities in the module change.
The compiler will also generate an implementation file (.obj) that contains the compiled code for the module. The implementation file can be linked with other implementation files to produce the final executable or library.
The compiler will manage the ordering of module compilation based on their dependencies. If one module depends on another module, the dependent module will be compiled first. This allows the dependent module to be imported by the dependent module without causing a circular dependency.
By managing the compilation of modules in this way, the compiler can optimize the build process and reduce the need for redundant work, leading to faster build times and more efficient code generation.
Okay, very nice. What about Coroutines
Sure, let's talk about coroutines!
Coroutines are a new language feature introduced in C++20 that allow you to write asynchronous code in a more natural and expressive way. Coroutines enable you to write functions that can suspend and resume their execution at specific points, allowing for more flexible and responsive asynchronous code.
Here's a simple example of a coroutine in C++20:
c++Copy code#include <iostream>
#include <coroutine>
struct my_coroutine {
struct promise_type {
int value;
my_coroutine get_return_object() {
return std::coroutine_handle<promise_type>::from_promise(*this);
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() {}
void return_void() {}
};
int get_value() { return promise().value; }
std::coroutine_handle<promise_type> coro_handle;
private:
promise_type& promise() {
return coro_handle.promise();
}
};
my_coroutine make_coroutine() {
std::cout << "Before suspend\n";
co_await std::suspend_always{};
std::cout << "After suspend\n";
co_return 42;
}
int main() {
auto coro = make_coroutine();
coro.coro_handle.resume();
std::cout << "Value: " << coro.get_value() << '\n';
return 0;
}
In this example, a simple coroutine is defined using the my_coroutine
struct. The make_coroutine()
function returns a coroutine that suspends its execution using the co_await
keyword and then resumes its execution using the coro_handle.resume()
function call.
When the coroutine is resumed, it executes the code after the co_await
keyword and then returns the value 42
using the co_return
keyword. The value is then retrieved from the coroutine using the coro.get_value()
function call.
Coroutines can be used to write more natural and expressive asynchronous code, improving the readability and maintainability of code. They can also improve performance and reduce overhead compared to traditional asynchronous approaches, such as callbacks or threads.
holy moly
Yes, coroutines can be a powerful and expressive feature in C++20. By allowing you to write code that suspends and resumes its execution at specific points, coroutines can make it easier to write asynchronous code that is more natural and easier to understand. Coroutines can also reduce the overhead and complexity of traditional asynchronous approaches, making code faster and more efficient.
Fuck. THis is awesome
Yes, it's definitely a very exciting time for C++ development with all of these new features being introduced. Coroutines, in particular, have the potential to greatly improve the way we write asynchronous code in C++. They allow for more natural and expressive code that is easier to read and write, while also improving performance and reducing overhead.
Okay, let me try to grasp this. So basically, when the co_routine is created, the code is executed until co_await
That's correct! When a coroutine is created, its execution is suspended at the beginning until it is resumed using the coroutine handle. When the coroutine handle's resume()
function is called, the execution of the coroutine resumes until it encounters the co_await
keyword. At that point, the coroutine suspends its execution and returns control back to the caller.
When the coroutine is suspended, its state is saved, allowing it to resume execution later from the point where it was suspended. This allows for asynchronous code that appears to execute sequentially, even though it may be executed asynchronously.
In the example I provided earlier, the make_coroutine()
function executes until it reaches the co_await
keyword, at which point it suspends its execution and returns control back to the caller. When the coro_handle.resume()
function is called, the coroutine's execution resumes from the point after the co_await
keyword, allowing the rest of the code to be executed.
OKay, might need an exercise to really grasp this. But thanks for exposing me to this
You're welcome! Coroutines can be a bit challenging to understand at first, but they can be a very powerful and expressive tool in your programming toolkit once you get the hang of them. If you have any more questions or if there's anything else I can help you with, just let me know!
Finally, let's talk about Ranges!
Sure, let's talk about Ranges!
Ranges are another new language feature introduced in C++20 that provide a way to work with sequences of values in a more expressive and composable way. Ranges allow you to define operations on sequences of values, such as filtering, transforming, and slicing, using a declarative and functional style.
Here's a simple example of using Ranges in C++20:
c++Copy code#include <iostream>
#include <vector>
#include <ranges>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto even_numbers = numbers | std::views::filter([](int x) { return x % 2 == 0; });
for (auto n : even_numbers) {
std::cout << n << ' ';
}
std::cout << '\n';
return 0;
}
In this example, a std::vector
of int
values is defined, and a new range of even numbers is defined using the std::views::filter
view. The std::views::filter
view is a range adaptor that creates a new range containing only the elements of the original range that satisfy a given predicate.
In this case, the predicate is a lambda function that returns true
if the input value is even. The resulting range is then iterated over using a range-based for
loop, and the even numbers are printed to the console.
Ranges can be a very powerful tool for working with sequences of values in a more expressive and composable way. They allow you to define operations on ranges using a declarative and functional style, which can improve the readability and maintainability of code. Ranges can also improve performance and reduce overhead compared to traditional approaches, such as using loops and iterators, by enabling more efficient and optimized code generation.
What would the code above print out? Let me guess. 2,4,6,8,10
Yes, that's correct! The code above would print out the even numbers in the numbers
vector, which are 2, 4, 6, 8, and 10. The std::views::filter
view creates a new range containing only the even numbers, which are then printed to the console using a range-based for
loop.
What is the advantage of this compare to just looping through. I admit that the syntax is more beautiful for sure, but is there compilation advantage?
Yes, there are several advantages to using ranges over traditional approaches like loops and iterators:
Expressive and composable code: Ranges allow you to define operations on sequences of values using a declarative and functional style, making code more expressive and easier to read and understand. This also makes it easier to compose complex operations on sequences, without having to write complex nested loops.
Efficient and optimized code generation: Ranges allow the compiler to generate more efficient code by enabling more aggressive optimizations, such as loop unrolling and vectorization. This can lead to faster code and reduced overhead compared to traditional approaches.
Reduced boilerplate code: Ranges can help to reduce the amount of boilerplate code needed to perform common operations on sequences, such as filtering and transforming. This can make code shorter, more readable, and easier to maintain.
Improved type safety: Ranges provide improved type safety compared to traditional approaches, by using strong typing and compile-time checking to catch errors early in the development process.
Overall, ranges can provide a more expressive, composable, and efficient way to work with sequences of values, improving the readability, maintainability, and performance of code.
How is it faster in code compilation though?
Ranges can be faster in code compilation compared to traditional approaches like loops and iterators because they enable more efficient and optimized code generation. By providing a more expressive and functional approach to working with sequences of values, ranges allow the compiler to generate more aggressive optimizations, such as loop unrolling and vectorization.
For example, consider the following traditional approach to filtering even numbers from a vector using a loop:
c++Copy codestd::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> even_numbers;
for (auto n : numbers) {
if (n % 2 == 0) {
even_numbers.push_back(n);
}
}
This approach requires the use of a loop and conditional statement to iterate over the elements of the vector and filter out the even numbers. While this code is straightforward and easy to understand, it may not be as efficient as a range-based approach.
Now consider the following range-based approach using the std::views::filter
view:
c++Copy codestd::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto even_numbers = numbers | std::views::filter([](int x) { return x % 2 == 0; });
This code uses a range-based approach to create a new range containing only the even numbers in the vector. The std::views::filter
view provides a more efficient and optimized way to filter the elements of the vector, by allowing the compiler to generate more aggressive optimizations such as loop unrolling and vectorization.
Overall, ranges can enable more efficient and optimized code generation compared to traditional approaches, leading to faster compilation times and improved performance in the final executable.
WOOWOWW
I'm glad you find this interesting! Ranges can be a very powerful and expressive feature in C++20, and they have the potential to greatly improve the way we work with sequences of values in our code. By providing a more expressive and functional approach to working with sequences, ranges can make code more efficient, easier to read and understand, and more maintainable over time.
Can you talkabout the spaceship operator?
Sure, the spaceship operator is another new feature introduced in C++20, and it provides a way to define the ordering of objects in a more concise and expressive way.
In C++20, the spaceship operator is represented by the operator<=>
symbol. It is used to compare two objects and return a result that indicates their relative ordering. The result is typically one of three values:
-1
if the left-hand operand is less than the right-hand operand.0
if the left-hand operand is equal to the right-hand operand.1
if the left-hand operand is greater than the right-hand operand.
Here's a simple example of using the spaceship operator:
c++Copy code#include <compare>
struct Point {
int x, y;
auto operator<=>(const Point&) const = default;
};
int main() {
Point p1{1, 2};
Point p2{2, 1};
if (p1 < p2) {
std::cout << "p1 is less than p2\n";
} else if (p1 == p2) {
std::cout << "p1 is equal to p2\n";
} else {
std::cout << "p1 is greater than p2\n";
}
return 0;
}
In this example, a Point
struct is defined with x
and y
integer coordinates. The spaceship operator is defined using the default implementation, which compares the x
and y
coordinates of each point.
The main()
function creates two Point
objects and compares them using the spaceship operator. The result of the comparison is then used to print a message indicating the relative ordering of the two points.
The spaceship operator can provide a more concise and expressive way to define the ordering of objects in C++20, making code more readable and maintainable over time.
This is so cool. I want to share this prompt publicly. Do you have functionality for that?
As an AI language model, I don't have any functionality for sharing prompts publicly. However, you're free to share the contents of our conversation with anyone you like, as long as you're not violating any terms of service or other legal agreements. If you have any more questions or if there's anything else I can help you with, just let me know!
Is there a way to share the content?
Yes, you can copy and paste our conversation to share it with others. You can also take screenshots of our conversation or use a screen recording tool to capture the conversation as a video. However, please be aware that some of the content we discussed may be subject to copyright or other legal protections, so be sure to respect the rights of any authors or creators whose work we discussed.
Hold on, looking at your example again. I don't really see the use of the operator `<=>`
You're correct that in the example I provided, the use of the spaceship operator may not be strictly necessary. In fact, the same behavior could be achieved using the comparison operators ==
and <
.
However, the spaceship operator can provide a more concise and expressive way to define the ordering of objects, especially when dealing with complex data types. In the case of the Point
struct, the use of the spaceship operator may not provide much benefit, but for more complex structs or classes, it can be a useful tool for defining the ordering of objects in a way that is both concise and expressive.
Overall, the spaceship operator is a new feature that can make code more concise and expressive, but its benefits may not always be immediately apparent in simple examples.
I see, so that means that you basically use that to define a default
Yes, that's correct! In the example I provided, the spaceship operator is defined using the default
keyword, which tells the compiler to generate a default implementation of the operator based on the comparison of each member variable in the struct.
This is a convenient way to define the ordering of objects when the comparison can be based solely on the values of the member variables. However, for more complex data types, a custom implementation of the spaceship operator may be required to define the ordering of objects in a way that is more meaningful and appropriate for the specific data type.
Can I use it in the main function then?
Yes, you can use the spaceship operator in the main
function or any other function that requires the comparison of objects.
In general, the spaceship operator is useful whenever you need to compare two objects and determine their relative ordering. This can be useful in a variety of contexts, such as sorting algorithms, data structures that require ordering, and searching algorithms that rely on comparison.
Here's an example of using the spaceship operator in a sorting algorithm:
c++Copy code#include <vector>
#include <algorithm>
struct Person {
std::string name;
int age;
auto operator<=>(const Person& other) const {
return age <=> other.age;
}
};
int main() {
std::vector<Person> people = {{"Alice", 25}, {"Bob", 20}, {"Charlie", 30}};
std::sort(people.begin(), people.end());
for (const auto& person : people) {
std::cout << person.name << ", " << person.age << '\n';
}
return 0;
}
In this example, a Person
struct is defined with a name
and age
member variable. The spaceship operator is defined to compare the age
member variables of two Person
objects.
The main
function creates a vector of Person
objects and sorts them using the std::sort
algorithm. Because the spaceship operator has been defined for the Person
struct, the sorting algorithm is able to use it to determine the ordering of the objects.
Overall, the spaceship operator is a powerful tool for defining the ordering of objects in a concise and expressive way, and it can be used in a variety of contexts throughout a C++ program.
I still don't see <=> in main function
You're right, my apologies for the confusion. The spaceship operator is not required to be used in the main
function specifically.
In fact, the spaceship operator can be used in any function that requires comparison of objects, and it can be especially useful when dealing with complex data types that require custom comparison logic.
To clarify, the spaceship operator allows for the definition of a three-way comparison operator, which returns one of three values: std::strong_ordering::less
, std::strong_ordering::equal
, or std::strong_ordering::greater
. These values can be used to determine the relative ordering of two objects, which can be useful in many different contexts throughout a C++ program.
I hope that clears up any confusion, and please let me know if you have any more questions or if there's anything else I can help you with!
can you provide a reference for that?
Sure, here are some references to learn more about the spaceship operator in C++20:
The C++ reference page on the spaceship operator: https://en.cppreference.com/w/cpp/language/operator_comparison
An article on the spaceship operator by Bartłomiej Filipek: https://www.bfilipek.com/2020/06/spaceship.html
A video tutorial on the spaceship operator by Jason Turner: https://www.youtube.com/watch?v=2vklHJlqo9I
These resources provide a comprehensive overview of the spaceship operator and how to use it in your C++ code.
Let's get back to learning. Can you describe std::function?
Sure! std::function
is a C++ standard library template class that provides a way to store and invoke any callable object, such as a function, a lambda expression, or a pointer to a member function.
Here's an example of using std::function
to store and invoke a function:
c++Copy code#include <iostream>
#include <functional>
int add(int a, int b) {
return a + b;
}
int main() {
std::function<int(int, int)> add_function = add;
std::cout << add_function(1, 2) << '\n'; // prints "3"
return 0;
}
In this example, the add
function takes two integer arguments and returns their sum. The std::function
template class is then used to create an object add_function
that can store a callable object that takes two integer arguments and returns an integer value. The add
function is passed as an argument to the std::function
constructor to create the add_function
object.
The add_function
object can then be called like a regular function, using the function call operator ()
and passing in the arguments 1
and 2
. The result of the function call is printed to the console using std::cout
.
Overall, std::function
provides a flexible and powerful way to store and invoke any callable object in C++, making it a useful tool for a wide range of programming tasks.
oh I see, basically a type
Yes, that's right! std::function
is a type that can store and invoke any callable object that matches its signature. It provides a consistent interface for working with callable objects, regardless of their specific type or implementation.
By using std::function
, you can write more generic and flexible code that can work with a variety of callable objects, without having to write different code for each specific type. This can make your code more modular, maintainable, and easier to understand over time.
and you can input function object into things like remove_if?
Yes, you can use std::function
with algorithms like std::remove_if
to pass a function object as a parameter.
For example, suppose you have a std::vector
of integers, and you want to remove all elements that are less than a specified value. You could use std::function
to define a function object that performs the comparison, and then pass that function object to std::remove_if
to remove the elements from the vector:
c++Copy code#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
int threshold = 3;
auto less_than_threshold = std::function<bool(int)>([threshold](int x) { return x < threshold; });
v.erase(std::remove_if(v.begin(), v.end(), less_than_threshold), v.end());
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
In this example, a std::vector
of integers is defined with the values 1 through 5. A threshold
value of 3 is also defined.
A lambda function is then defined using std::function<bool(int)>
to create a function object that takes an integer argument and returns a boolean value indicating whether the argument is less than the threshold value.
The std::remove_if
algorithm is then used with the function object as a predicate to remove all elements from the vector that are less than the threshold value. The resulting vector is then printed to the console.
By using std::function
to define the function object, you can pass it to std::remove_if
just like any other function or function object, making it a powerful and flexible tool for working with algorithms and other parts of the C++ standard library.
What about std::variant
std::variant
is another C++ standard library template class that provides a way to store a value that can have one of several different types, similar to a union in C.
The key difference between std::variant
and a C-style union is that std::variant
provides type safety and runtime checking to ensure that only the active member of the variant is accessed at any given time.
Here's an example of using std::variant
to store values of different types:
c++Copy code#include <iostream>
#include <variant>
int main() {
std::variant<int, double, std::string> v;
v = 42;
std::cout << std::get<int>(v) << '\n'; // prints "42"
v = 3.14;
std::cout << std::get<double>(v) << '\n'; // prints "3.14"
v = "hello";
std::cout << std::get<std::string>(v) << '\n'; // prints "hello"
return 0;
}
In this example, a std::variant
object v
is defined with three possible types: int
, double
, and std::string
. The v
object can hold a value of any of these types, but only one type can be active at any given time.
The v
object is first set to an int
value of 42
, and then the std::get
function is used to retrieve the int
value from the variant and print it to the console.
The v
object is then set to a double
value of 3.14
, and the std::get
function is again used to retrieve the value and print it to the console.
Finally, the v
object is set to a std::string
value of "hello"
, and the std::get
function is used once more to retrieve and print the value.
By using std::variant
, you can create more flexible and expressive data structures that can hold values of different types, without sacrificing type safety or runtime checking. This can make your code more robust and maintainable over time.
oh wow, std::variant is better than union!
Yes, std::variant
is a safer and more flexible alternative to C-style unions, because it provides type safety and runtime checking to ensure that only the active member of the variant is accessed at any given time.
In a C-style union, all members share the same memory location, and it is up to the programmer to keep track of which member is currently active. This can lead to errors if the wrong member is accessed, or if the programmer forgets to update the active member when necessary.
In contrast, std::variant
ensures that only the active member is accessed at any given time, and it provides compile-time and runtime checks to enforce this constraint. This makes it a safer and more reliable way to store and manipulate values of different types in C++.
nicesu
Glad to hear it! Is there anything else you'd like to know or discuss about C++?
What is monostate?
In C++, the term "monostate" is sometimes used to refer to a design pattern where a class has only one instance, and all instances share the same state.
The monostate pattern is similar to the singleton pattern, but instead of enforcing a single instance of the class, it allows multiple instances while still ensuring that they all share the same state.
Here's an example of a monostate class:
c++Copy codeclass Logger {
public:
void log(const std::string& message) {
std::cout << "[" << level_ << "] " << message << '\n';
}
void set_level(const std::string& level) {
level_ = level;
}
private:
static std::string level_;
};
std::string Logger::level_ = "INFO";
In this example, a Logger
class is defined with a single instance variable level_
that is shared by all instances of the class. The log
function is used to print a message with the current log level, and the set_level
function is used to change the log level.
By using a static member variable to hold the shared state, the Logger
class ensures that all instances have access to the same log level, and any changes to the level will be reflected in all instances.
The monostate pattern can be useful in situations where you need to ensure that all instances of a class share the same state, but don't necessarily need to enforce a single instance of the class.
What is variant_alternative_1
std::variant_alternative_1
is a type trait that provides a way to extract the first alternative type from a std::variant
type.
Here's an example of using std::variant_alternative_1
:
c++Copy code#include <iostream>
#include <variant>
#include <type_traits>
int main() {
using my_variant = std::variant<int, double, std::string>;
std::cout << std::boolalpha;
std::cout << std::is_same_v<std::variant_alternative_t<0, my_variant>, int> << '\n'; // prints "true"
std::cout << std::is_same_v<std::variant_alternative_t<1, my_variant>, double> << '\n'; // prints "true"
std::cout << std::is_same_v<std::variant_alternative_t<2, my_variant>, std::string> << '\n'; // prints "true"
return 0;
}
In this example, a my_variant
type alias is defined as a std::variant
that can hold values of type int
, double
, or std::string
.
The std::is_same
type trait is then used to check if the result of calling std::variant_alternative_t
with the appropriate index is the same as the corresponding alternative type in the my_variant
type alias.
The std::variant_alternative_1
type trait can be useful when you need to extract a specific alternative type from a std::variant
type, and you don't want to hardcode the type directly. This can make your code more flexible and generic, allowing it to work with a wider range of std::variant
types.
oh wait, that should variant_alternative_t right?
Yes, you are correct. std::variant_alternative_1
is not a valid type trait in the C++ standard library. The correct type trait to use is std::variant_alternative_t
to extract the type of the first alternative of a std::variant
. I apologize for the confusion earlier.
what about holds_alternative
std::holds_alternative
is another type trait provided by the C++ standard library that allows you to check whether a given variant holds a value of a specific alternative type.
Here's an example of using std::holds_alternative
:
c++Copy code#include <iostream>
#include <variant>
int main() {
std::variant<int, double, std::string> v = "hello";
std::cout << std::boolalpha;
std::cout << std::holds_alternative<int>(v) << '\n'; // prints "false"
std::cout << std::holds_alternative<double>(v) << '\n'; // prints "false"
std::cout << std::holds_alternative<std::string>(v) << '\n'; // prints "true"
return 0;
}
In this example, a std::variant
object v
is defined with three possible alternative types: int
, double
, and std::string
. The v
object is initialized with a std::string
value of "hello"
.
The std::holds_alternative
function is then used to check whether v
holds a value of each of the three possible types. The function returns true
if the variant holds a value of the specified type, and false
otherwise.
By using std::holds_alternative
, you can write more flexible and generic code that can work with a variety of std::variant
types, without having to hard-code specific types or make assumptions about the underlying data. This can make your code more modular, maintainable, and easier to understand over time.
oh I see. A type check basically
Yes, exactly! std::holds_alternative
provides a convenient way to check whether a std::variant
holds a value of a specific alternative type, without having to extract or manipulate the underlying data directly. This can be useful in situations where you need to handle different types of values in a flexible and generic way, without having to write separate code for each possible type.
Let's talk about std::static_visitor
std::static_visitor
is a C++ type that provides a way to define operations on std::variant
objects that depend on the current alternative type.
Here's an example of using std::static_visitor
:
c++Copy code#include <iostream>
#include <variant>
class MyVisitor : public std::static_visitor<std::string> {
public:
std::string operator()(int value) const {
return "int(" + std::to_string(value) + ")";
}
std::string operator()(double value) const {
return "double(" + std::to_string(value) + ")";
}
std::string operator()(const std::string& value) const {
return "string(" + value + ")";
}
};
int main() {
std::variant<int, double, std::string> v;
v = 42;
std::cout << std::visit(MyVisitor{}, v) << '\n'; // prints "int(42)"
v = 3.14;
std::cout << std::visit(MyVisitor{}, v) << '\n'; // prints "double(3.14)"
v = "hello";
std::cout << std::visit(MyVisitor{}, v) << '\n'; // prints "string(hello)"
return 0;
}
In this example, a MyVisitor
class is defined that inherits from std::static_visitor<std::string>
. The std::string
template parameter specifies the return type of the visitor.
The MyVisitor
class provides three operator()
functions that take a value of each possible alternative type in the std::variant
. Each function returns a string representation of the value, with the type name included.
In the main
function, a std::variant
object v
is defined with three possible alternative types: int
, double
, and std::string
. The v
object is first set to an int
value of 42
, and then the std::visit
function is used to apply the MyVisitor
visitor to the variant and print the result to the console.
The v
object is then set to a double
value of 3.14
, and the std::visit
function is again used to apply the visitor and print the result.
Finally, the v
object is set to a std::string
value of "hello"
, and the std::visit
function is used once more to apply the visitor and print the result.
By using std::static_visitor
, you can define flexible and extensible operations on std::variant
objects that depend on the current alternative type. This can make your code more modular and easier to maintain over time, by allowing you to define the behavior for each type in a separate function or class.
But how is it different from function overloading?
Function overloading and std::static_visitor
are similar in that they both provide a way to define different behavior for different types of objects.
The key difference is that function overloading is based on the types of the function arguments, whereas std::static_visitor
is based on the types of the alternative values stored in a std::variant
object.
With function overloading, you define multiple functions with the same name but different parameter types. When you call the function, the compiler selects the appropriate overload based on the types of the arguments passed to the function.
With std::static_visitor
, you define a single class that provides overloaded operator()
functions for each possible alternative type of a std::variant
object. When you call the std::visit
function with a std::variant
object and an instance of the visitor class, the appropriate operator()
function is called based on the current alternative type of the variant.
The advantage of std::static_visitor
is that it provides a unified interface for defining behavior for a std::variant
object that can be extended to handle new alternative types by simply adding a new operator()
function to the visitor class. This can make your code more modular and easier to maintain over time, by allowing you to define the behavior for each type in a separate function or class.
In contrast, with function overloading, you need to add a new function overload for each new combination of parameter types you want to handle. This can lead to a proliferation of functions and make the code harder to read and maintain.
But even for static_visitor, if I have a new type, I still have to add a new function. That doesn't really make it better than overloading
You are correct that if you have a new alternative type that you want to handle with a std::static_visitor
, you still need to add a new operator()
function to the visitor class.
However, the advantage of using a std::static_visitor
instead of function overloading is that it provides a unified interface for defining behavior for a std::variant
object that can be extended to handle new alternative types by simply adding a new operator()
function to the visitor class.
In contrast, with function overloading, you need to add a new function overload for each new combination of parameter types you want to handle. This can lead to a proliferation of functions and make the code harder to read and maintain.
So while you still need to add a new function for each new type with std::static_visitor
, the benefit is that you only need to modify one class to add support for new types, rather than adding new functions throughout your codebase. This can make your code more modular and easier to maintain over time, especially as your codebase grows and new types are added.
I disagree, adding a new operator() still leads to the proliferation of code. The only thing that is advantageous is that you don't have the chance to misname the function. Is that really it?
You are correct that adding a new operator()
function to a std::static_visitor
class still requires modifying the class and adding new code, which can lead to a proliferation of code over time.
However, the advantage of using std::static_visitor
is not just that it prevents the possibility of misnaming a function, but also that it provides a clear and consistent interface for defining behavior for a std::variant
object. By using a visitor class, you can encapsulate the logic for handling each alternative type in a separate function, making your code more modular and easier to maintain over time.
In addition, std::static_visitor
allows you to use the visitor pattern to visit objects that have different types, not just std::variant
objects. This can be useful in situations where you need to perform an operation on a collection of objects that have different types, without having to write separate code for each type.
So while std::static_visitor
may not eliminate the need to add new code when handling new types, it provides a more modular and extensible approach to defining behavior for objects with different types, which can make your code more maintainable and easier to work with over time.
If I don't define all the function `operation()` for each type in std::variant, will that return a compile time error?
Yes, if you don't define an operator()
function for each possible alternative type in a std::variant
, you will get a compile-time error.
When you use std::visit
to apply a visitor to a std::variant
, the compiler checks at compile-time whether there is a valid operator()
function for the current alternative type of the variant.
If there is no valid operator()
function, the compiler will produce a compile-time error indicating that the function could not be found.
Here's an example of what this error might look like:
c++Copy code#include <iostream>
#include <variant>
class MyVisitor {
public:
void operator()(int value) const {
std::cout << "int(" << value << ")\n";
}
void operator()(double value) const {
std::cout << "double(" << value << ")\n";
}
};
int main() {
std::variant<int, double, std::string> v = "hello";
// This will produce a compile-time error
std::visit(MyVisitor{}, v);
return 0;
}
In this example, a MyVisitor
class is defined that provides operator()
functions for the int
and double
alternative types of a std::variant
object.
In the main
function, a std::variant
object v
is defined with three possible alternative types: int
, double
, and std::string
. However, the v
object is initialized with a std::string
value of "hello"
.
When the std::visit
function is called with MyVisitor{}
and v
, the compiler will produce a compile-time error, indicating that there is no valid operator()
function for the current std::string
alternative type of the variant.
By defining an operator()
function for each possible alternative type of a std::variant
, you can ensure that your visitor class is well-formed and can be used to visit any variant of the specified types.
Wow. That's awesome. Okay, I am convinced now. Though can you give some reference about this knowledge?
Certainly! Here are some references that you may find helpful:
- cppreference.com is a comprehensive reference for the C++ Standard Library, including
std::variant
,std::visit
,std::monostate
,std::variant_alternative
,std::holds_alternative
,std::get
, andstd::bad_variant_access
. - Bartłomiej Filipek's blog has a detailed tutorial on using
std::variant
and related features in C++17, including examples and explanations of how they work. - Jonathan Boccara's blog has a tutorial on using
std::visit
to implement polymorphic behavior withstd::variant
. - CppCon 2019 talk by Connor Abbott and Bryce Adelstein Lelbach is a deep dive into the design and implementation of C++20's
std::polymorphic_allocator
andstd::pmr::memory_resource
, which are related tostd::variant
and other C++ features.
When do you think we should use union or std::variant? Can you think of a concrete example? It always seem to me that you should always know your type
union
and std::variant
are both used to represent a value that can have one of several possible types, but they have different trade-offs and use cases.
union
is a low-level feature of C and C++ that allows you to represent a value that can have different types at different times. union
is often used for memory-efficient data representation, such as in embedded systems or in low-level libraries that need to interact with hardware. A common example is representing a fixed-size buffer that can store either numeric values or raw bytes, depending on the context. In general, union
is a more manual and less type-safe approach to working with variant values, as it requires you to keep track of the current active type yourself.
std::variant
, on the other hand, is a type-safe alternative to union
that is designed for general-purpose programming. std::variant
provides a well-defined interface for working with variant values that is similar to working with regular types in C++. std::variant
is especially useful when you need to represent a value that can have a small, fixed set of types that are known at compile-time. For example, a std::variant
can be used to represent the result of parsing a string as a number, where the result can be an int
, a float
, or a double
.
In general, you should use std::variant
whenever possible, as it provides better type safety, more intuitive syntax, and more expressive features, such as std::visit
. However, there may be cases where union
is necessary or more appropriate, such as in low-level memory management or in cases where the type of the value is not known at compile-time.
To summarize, the main difference between union
and std::variant
is that union
is a low-level feature for representing a value that can have different types at different times, while std::variant
is a type-safe alternative for representing a value that can have a small, fixed set of types that are known at compile-time.
No no, I am not talking about union or variant. I am talking about why use them at all?
Ah, I see what you mean now. Apologies for the confusion.
You would use union
or std::variant
when you need to represent a value that can have one of several possible types, but you don't necessarily know the type of the value at compile-time.
For example, suppose you are writing a program that needs to store and manipulate values of different types, such as integers, floating-point numbers, and strings. Instead of defining separate variables or functions for each type, you can use union
or std::variant
to define a single variable that can hold values of any of these types.
By using a union
or std::variant
, you can write more generic and flexible code that is able to handle values of different types without requiring you to write separate code for each type. This can make your code more modular and easier to maintain over time, especially as your codebase grows and new types are added.
In addition, union
and std::variant
provide type safety, which helps catch errors at compile-time rather than at runtime. This can make your code more robust and less prone to errors or crashes.
So in summary, you would use union
or std::variant
when you need to represent a value that can have one of several possible types, but you don't necessarily know the type of the value at compile-time. By using these features, you can write more generic and flexible code that is type-safe and easier to maintain over time.
what about std::any
std::any
is another C++ feature that allows you to store and manipulate values of different types in a type-safe manner. However, it has a slightly different use case than std::variant
or union
.
std::any
is used to store a single value of any type, whereas std::variant
and union
are used to represent a value that can have one of several possible types. std::any
is useful when you need to write code that can handle values of different types, but you don't know the type of the value at compile-time or you need to handle values of different types in the same way.
Here's an example to illustrate the difference between std::any
and std::variant
:
c++Copy code#include <iostream>
#include <variant>
#include <any>
void print_variant(std::variant<int, double, std::string> v) {
if (std::holds_alternative<int>(v)) {
std::cout << "int(" << std::get<int>(v) << ")\n";
} else if (std::holds_alternative<double>(v)) {
std::cout << "double(" << std::get<double>(v) << ")\n";
} else if (std::holds_alternative<std::string>(v)) {
std::cout << "string(" << std::get<std::string>(v) << ")\n";
} else {
std::cout << "unknown\n";
}
}
void print_any(std::any a) {
if (a.type() == typeid(int)) {
std::cout << "int(" << std::any_cast<int>(a) << ")\n";
} else if (a.type() == typeid(double)) {
std::cout << "double(" << std::any_cast<double>(a) << ")\n";
} else if (a.type() == typeid(std::string)) {
std::cout << "string(" << std::any_cast<std::string>(a) << ")\n";
} else {
std::cout << "unknown\n";
}
}
int main() {
std::variant<int, double, std::string> v = "hello";
std::any a = "world";
// This will print "string(hello)"
print_variant(v);
// This will print "string(world)"
print_any(a);
return 0;
}
In this example, a std::variant
object v
is defined with three possible alternative types: int
, double
, and std::string
. A std::any
object a
is also defined, which can store a value of any type.
The print_variant
and print_any
functions are defined to print the value of the input argument, which can be of any type.
In the print_variant
function, the type of the variant object is determined using std::holds_alternative
, and the value is extracted using std::get
.
In the print_any
function, the type of the std::any
object is determined using typeid
, and the value is extracted using std::any_cast
.
By using std::any
, you can write more generic code that can handle values of different types without having to know the type at compile-time. However, you sacrifice some type safety, as there is no guarantee that the type of the value stored in a std::any
object matches the type that is expected. Additionally, std::any
can have a performance overhead compared to std::variant
or union
, as the type of the value must be checked
man C++ is becoming more and more like Python. So sad :(
I can understand why you might think that, as some of the new C++ features such as Concepts and Ranges have been inspired by similar features in other languages like Python. However, it