c++templatesboostboost-units

Using boost units: Is it possible to formulate a cast to dimensionless for composite types


We want to use boost::units for strong types but also resort to dimensionless types for compatibility e.g. with other libraries. Is there any way to implement this in a generic fashion ? Something along the lines of the following code (which obviously does not compile)

#include <boost/units/systems/si.hpp>
using namespace boost::units;
using SiTime = quantity<boost::units::si::time, double>;
using SiLength = quantity<boost::units::si::length, double>;

template<typename TIME,typename LENGTH>
struct Kernel{
    using   time_t                =  TIME;
    using   length_t              =  LENGTH;    
};
using KernelD = Kernel<double,double>;
using KernelSi = Kernel<SiTime,SiLength>;

template<typename TO_KERNEL>
struct composite_cast{
    template<template <typename> class COMPOSITE,typename FROM_KERNEL>
    COMPOSITE<TO_KERNEL> operator()(COMPOSITE<FROM_KERNEL> value){         
        (void)value;
        //how could this work ?
        return COMPOSITE<TO_KERNEL>();
    }  
};

template<typename KERNEL>
struct Foo{
    typename KERNEL::length_t length;
    typename KERNEL::time_t time;
};   
using FooSi = Foo<KernelSi>;
using FooD = Foo<KernelD>;

template<typename KERNEL>
struct Bar{
    Foo<KERNEL> a;
    Foo<KERNEL> b;    
};   
using BarSi = Bar<KernelSi>;
using BarD = Bar<KernelD>;

int main(int, char**) {
    FooSi fooSi;
    FooD fooD =  composite_cast<KernelD>(fooSi);
    BarSi barSi;
    BarD barD =  composite_cast<KernelD>(barSi);    
    return 0;
}

Solution

  • Besides the litany of syntax errors in your sample code, this code won't work because COMPOSITE needs to be a template-template argument type:

    template <typename TO_KERNEL> struct composite_cast {
        template <template <typename> class COMPOSITE, typename FROM_KERNEL>
        COMPOSITE<TO_KERNEL> operator()(COMPOSITE<FROM_KERNEL> value) {
            // how could this work ?
            return {};
        }
    };
    

    That said, I'd go for explicit conversions here:

    Live On Coliru

    #include <boost/units/systems/si.hpp>
    using namespace boost::units;
    using SiTime = quantity<boost::units::si::time, double>;
    using SiLength = quantity<boost::units::si::length, double>;
    
    template <typename TIME, typename LENGTH> struct Kernel {
        using time_t = TIME;
        using length_t = LENGTH;
    };
    
    using KernelD  = Kernel<double, double>;
    using KernelSi = Kernel<SiTime, SiLength>;
    
    template <typename KERNEL> struct Foo {
        typename KERNEL::length_t length;
        typename KERNEL::time_t time;
    
        template <typename K2> explicit operator Foo<K2>() const { return { 
            quantity_cast<typename K2::length_t>(length),
            quantity_cast<typename K2::time_t>(time) };
        }
    };
    
    using FooSi = Foo<KernelSi>;
    using FooD = Foo<KernelD>;
    
    template <typename KERNEL> struct Bar {
        Foo<KERNEL> a;
        Foo<KERNEL> b;
    
        template <typename K2> explicit operator Bar<K2>() const { 
            return { static_cast<Foo<K2>>(a), static_cast<Foo<K2>>(b) };
        }
    };
    
    using BarSi = Bar<KernelSi>;
    using BarD = Bar<KernelD>;
    
    int main() {
        FooSi fooSi;
        FooD fooD = static_cast<FooD>(fooSi);
        BarSi barSi;
        BarD barD = static_cast<BarD>(barSi);
    }
    

    Explicit Conversion Functions

    If you don't want to have the member operators, make them free:

    Live On Coliru

    #include <boost/units/systems/si.hpp>
    using namespace boost::units;
    using SiTime = quantity<boost::units::si::time, double>;
    using SiLength = quantity<boost::units::si::length, double>;
    
    template <typename TIME, typename LENGTH> struct Kernel {
        using time_t = TIME;
        using length_t = LENGTH;
    };
    
    using KernelD  = Kernel<double, double>;
    using KernelSi = Kernel<SiTime, SiLength>;
    
    template <typename KERNEL> struct Foo {
        typename KERNEL::length_t length;
        typename KERNEL::time_t time;
    };
    
    using FooSi = Foo<KernelSi>;
    using FooD = Foo<KernelD>;
    
    template <typename KERNEL> struct Bar {
        Foo<KERNEL> a;
        Foo<KERNEL> b;
    };
    
    template <typename K2, typename K1>
    Foo<K2> kernel_cast(Foo<K1> const& foo) {
        return { 
            quantity_cast<typename K2::length_t>(foo.length),
            quantity_cast<typename K2::time_t>(foo.time) 
        };
    }
    
    template <typename K2, typename K1>
    Bar<K2> kernel_cast(Bar<K1> const& bar) {
        return { kernel_cast<K2>(bar.a), kernel_cast<K2>(bar.b) };
    }
    
    using BarSi = Bar<KernelSi>;
    using BarD = Bar<KernelD>;
    
    int main() {
        FooSi fooSi;
        FooD fooD = kernel_cast<KernelD>(fooSi);
        BarSi barSi;
        BarD barD = kernel_cast<KernelD>(barSi);
    }
    

    Have Your Cake And Eat It Too

    Incidentally, the free-function approach ties in really well with your original composite_cast function object:

    template <typename TO_KERNEL> struct composite_cast {
        template <template <typename> class COMPOSITE, typename FROM_KERNEL>
        COMPOSITE<TO_KERNEL> operator()(COMPOSITE<FROM_KERNEL> const& value) const {
            return kernel_cast<TO_KERNEL>(value);
        }
    };
    
    int main() {
        composite_cast<KernelD> caster;
    
        FooSi fooSi;
        FooD fooD = caster(fooSi);
        BarSi barSi;
        BarD barD = caster(barSi);
    }