Search code examples
c++boost-mplboost-mp11

Template metaprogramming - trying to implement Dimensional Analysis


For a better understanding and learning, I tried to use the boost mp11 library to implement the Dimensional Analysis from "C++ Template Metaprogramming" book (also found in the documentation of boost mpl library). A reason why was that C++11 has features that weren't available when the book was written, features that are very useful now.

I am using Visual Studio 2019, community version.

All went well until I had to implement the operator*. I couldn't perform the trick that was used for the mpl library. Operator+ worked well, so the problem must be similar to the one pointed out in the book, but should have a different solution. Please, help me to find it.

The (shortest possible) example code is below. It gets compiled and runs correctly. When the last 3 lines are uncommented, one gets the error:

error C2676: binary '*': 'quantity<double,mass>' does not define this operator or a conversion to a type acceptable to the predefined operator

For some reason, operator* is invisible, while similar operator+ is.

Thank you

#include <iostream>

typedef std::integer_sequence<int, 1, 0, 0, 0, 0, 0, 0> mass;
typedef std::integer_sequence<int, 0, 1, 0, 0, 0, 0, 0> length;
typedef std::integer_sequence<int, 0, 0, 1, 0, 0, 0, 0> time;

typedef std::integer_sequence<int, 0, 1, -1, 0, 0, 0, 0> velocity;
typedef std::integer_sequence<int, 0, 1, -2, 0, 0, 0, 0> acceleration;
typedef std::integer_sequence<int, 1, 1, -2, 0, 0, 0, 0> force;


template<typename T, typename D>
struct quantity
{
    template<typename D1>
    quantity(const quantity<T, D1>& oOther) :
        m_value(oOther.m_value())
    {
        static_assert(std::is_same<D, D1>::value, "Type mismatch");
    }

    explicit quantity(T x) :
        m_value(x)
    {
    }

    T value() const
    {
        return m_value;
    }

    private:
        T m_value;
};


template
<
    template<class... A> class F,
    class... L
>
struct mp_transform_impl;

template
<
    template<class...> class F,
    template<class...> class L1,
    class... T1,
    template<class...> class L2,
    class... T2
>
struct mp_transform_impl<F, L1<T1...>, L2<T2...>>
{
    using type = L1<F<T1, T2>...>;
};

template<template<class...> class F, class... L>
using mp_transform = typename mp_transform_impl<F, L...>::type;


template<class... T>
struct mp_plus_impl;

template<>
struct mp_plus_impl<>
{
    using type = std::integral_constant<int, 0>;
};

template<class T1, class... T>
struct mp_plus_impl<T1, T...>
{
    static constexpr auto _v = T1::value + mp_plus_impl<T...>::type::value;
    using type = std::integral_constant<typename std::remove_const<decltype(_v)>::type, _v>;
};

template<class... T>
using mp_plus = typename mp_plus_impl<T...>::type;


template<typename T, typename D>
quantity<T, D> operator+(quantity<T, D> x, quantity<T, D> y)
{
    return quantity<T, D>(x.value() + y.value());
}

template<typename T, typename D1, typename D2>
quantity
<
    T,
    typename mp_transform<mp_plus, D1, D2>::type
>
operator*(quantity<T, D1> x, quantity<T, D2> y)
{
    using dim = typename mp_transform<mp_plus, D1, D2>::type;
    return quantity<T, dim>(x.value() * y.value());
}


int main(int argc, char* argv[])
{
    auto len1 = quantity<double, length>(4.5);
    auto len2 = quantity<double, length>(3.5);
    std::cout <<
        "Sum: " <<
        (len1 + len2).value() <<
        std::endl;

    auto m = quantity<double, mass>(5.0);
    auto a = quantity<double, acceleration>(9.815);

    // error C2676:  binary '*': 'quantity<double,mass>' does not define this operator
    // or a conversion to a type acceptable to the predefined operator
    //std::cout <<
    //    "Product: " <<
    //    (m * a).value() << std::endl;
    return 0;
}

Solution

  • There are multiple problems with the code. After fixing some, but not all, here is a working version: https://godbolt.org/z/3B9Sc4


    Here are some of the modifications:

    1. The use of std::integral_constant is unnecessary. You can simply use constexpr ints. This allows simplifying your mp_plus significantly, and:

    2. The template parameters in mp_transform_impl are needlessly complicated. You don't need classes L1, L2 when always working with std::integer_sequence<int, ...>.

    3. With your mp_transform you don't need to add typename and ::type.