Modern Features in C++17

Non-virtual runtime polymorphism can be achieved with modern C++ (e.g., C++17) features std::any and std::variant as described in the table below.

Notice std::tuple is not used for polymorphism; it offers a structured way to manage multiple values of different types simultaneously, such as in function return types, or parameter packs. It is put here because of its usage is a bit similar to std::any and std::variant.

FeaturePaired WithPurposeUse CaseSpecial Notes
std::anystd::any_castTo store an instance of any type and safely retrieve the stored value.Use std::any when the exact type is unknown or may change, and std::any_cast for retrieval.Type-safe storage for single values. std::any_cast throws an exception if the cast is to the wrong type.
std::variantstd::visitTo store one of several predefined types and apply a function to the variant’s content.Use std::variant when the value can be one of a few known types and std::visit for operations.Type-safe and more constrained than std::any. std::visit requires a callable applicable to all types in the variant.
std::tuplestd::applyTo store a fixed-size collection of heterogeneous values and apply a function to them.Use std::tuple for grouping known types and std::apply to call a function with tuple elements.Accessed by index using std::get. std::apply useful for calling a function with tuple elements as arguments.

Code Example

std::any

#include <any>
#include <iostream>
#include <string>

int main() {
    std::any a = 1; // Store an int
    std::cout << std::any_cast<int>(a) << std::endl; // Extract the int

    a = std::string("Hello, World!"); // Store a string
    std::cout << std::any_cast<std::string>(a) << std::endl; // Extract the string

    return 0;
}

std::variant

#include <variant>
#include <iostream>
#include <string>

int main() {
    // A variant that can hold either an int, double, or string
    std::variant<int, double, std::string> var;

    var = 10; // Assigning an integer
    // Visit the variant and apply a lambda function
    std::visit([](auto&& arg) {
        std::cout << arg << std::endl;
    }, var);

    var = "Hello, World!"; // Now assign a string
    // Visit the variant again
    std::visit([](auto&& arg) {
        std::cout << arg << std::endl;
    }, var);

    return 0;
}

std::tupple

#include <tuple>
#include <iostream>

// A function to be applied to a tuple
void print(int i, const std::string& s, double d) {
    std::cout << i << ", " << s << ", " << d << std::endl;
}

int main() {
    std::tuple<int, std::string, double> t = std::make_tuple(42, "Hello", 3.14);

    // Apply the function 'print' to the tuple 't'
    std::apply(print, t);

    return 0;
}

CRTP in C++98

The Curiously Recurring Template Pattern (CRTP) is an advanced C++ idiom that involves a particular form of inheritance that leverages templates. It achieves static polymorphism, which allows for polymorphic behavior without the overhead of dynamic polymorphism (such as virtual functions).

In CRTP, a template class named Base is designed to take a derived class as its template parameter. When defining a derived class, such as Derived, it inherits from Base by passing itself as the template argument (Base<Derived>). This allows Base to define an interface which Derived implements, enabling Base to invoke these methods directly.

The CRTP enables static (compile-time) polymorphism. By using templates, the base class can call methods of the derived class without needing virtual functions. This is because the base class knows the type of the derived class at compile time.

Unlike dynamic polymorphism, which uses virtual functions to resolve method calls at runtime, CRTP resolves them at compile time. This eliminates the overhead associated with dynamic dispatch (like v-table lookups).

Code Example

template<typename Derived>
class Base {
public:
    void interface() {
        // ... common pre-processing ...

        // Call to the derived class's implementation
        static_cast<Derived*>(this)->implementation();

        // ... common post-processing ...
    }
};

class Derived : public Base<Derived> {
public:
    void implementation() {
        // Specific implementation
    }
};

Explicit Object Parameter in C++23

Thanks to the explicit object parameter (Deducing this), the C and the R can be removed from the CRTP acronym:

C++23’s explicit object parameter feature allows the class name to be referenced explicitly in the parameter lists of non-static member functions. With Deducing this, the type of the explicit object parameter can be deduced to the derived type and perfectly forwarded. As a result, template non-static member functions can access the derived class, eliminating the need to specialize the object over the derived class.

Code Example

class Base {
public:
    template<typename T>
    void interface(this T&& self) {
        // ... common pre-processing ...

        // Call to the derived class's implementation
        self.implementation();

        // ... common post-processing ...
    }
};

class Derived : public Base {
public:
    void implementation() {
        // Specific implementation
    }
};

Reference

https://www.modernescpp.com/index.php/c23-syntactic-sugar-with-deducing-this/

https://www.youtube.com/watch?v=xpomlTd41hg&list=PL6NrtSSUk9t9M_Rk-6YkW9u5RJym-SuWZ&index=1&t=2626s