C++ is now a feature-rich language, Be aware of OverEngeniering

Being aware of overengineering is crucial when working with a feature-rich language like C++. Overengineering occurs when developers introduce overly complex or unnecessary solutions to a problem.

C++ developers could be attempted to use as possible the new features introduced by the new standards. which makes the code finally more complicated than it must be.

Here’s an example to show how C++ metaprogramming can be used to create a type-erased container with arithmetic operations that are evaluated at compile time. While this example show the power and flexibility of C++ metaprogramming techniques. it might seem complicated due to the use of templates, concepts and constexpr functions:

#include <iostream>
#include <type_traits>

template<typename T>
concept Arithmetic = std::is_arithmetic_v<T>;

template<Arithmetic T>
struct AnyType {
    constexpr AnyType(const T& value) : value_(value) {}

    template<Arithmetic U>
    constexpr auto add(const AnyType<U>& other) const {
        return AnyType{ value_ + other.value_ };
    }

    template<Arithmetic U>
    constexpr auto subtract(const AnyType<U>& other) const {
        return AnyType{ value_ - other.value_ };
    }

    template<Arithmetic U>
    constexpr auto multiply(const AnyType<U>& other) const {
        return AnyType{ value_ * other.value_ };
    }

    template<Arithmetic U>
    constexpr auto divide(const AnyType<U>& other) const {
        static_assert(other.value_ != 0, "Division by zero");
        return AnyType{ value_ / other.value_ };
    }

    template<Arithmetic U>
    friend std::ostream& operator<<(std::ostream& os, const AnyType<U>& any) {
        return os << any.value_;
    }

private:
    T value_;
};

int main() {
    constexpr AnyType<int> x{ 5 };
    constexpr AnyType<float> y{ 2.5f };

    constexpr auto addition = x.add(y);
    constexpr auto subtraction = x.subtract(y);
    constexpr auto multiplication = x.multiply(y);
    constexpr auto division = x.divide(y);

    std::cout << "Addition: " << addition << std::endl;
    std::cout << "Subtraction: " << subtraction << std::endl;
    std::cout << "Multiplication: " << multiplication << std::endl;
    std::cout << "Division: " << division << std::endl;

    return 0;
}

In this example:

  • We define a concept Arithmetic to constrain the template parameters to arithmetic types.
  • The AnyType class template is defined to hold any arithmetic type.
  • We provide member functions (add, subtract, multiply, divide) that perform arithmetic operations between AnyType objects of potentially different types.
  • We use constexpr to ensure that these operations are evaluated at compile time.
  • We overload the operator<< to allow streaming AnyType objects to std::ostream.

Yes sometimes for specific needs we can develop a code like that, But in general do we need this sophisticated class to do basic arithmetic operations, it’s like construct a tank to kill a fly 🙂

To resume try to not be tempted by the sophiscated new features and try to use them only if you need to. And always try to Follow KISS and YAGNI Principles:

Keep It Simple, Stupid (KISS) and You Ain’t Gonna Need It (YAGNI) are principles that advocate for simplicity and avoiding unnecessary features until they are needed.