Home > front end >  Bug in templated conversion operator in GCC: Workaround?
Bug in templated conversion operator in GCC: Workaround?

Time:12-01

I want to have a class representing a unit with some kind of dimension. This should express something like 1.5m^2. A scalar multiplication with some type shall be allowed and a dimensionless unit should behave exactly like the underlying type. Here is my solution:

#include <type_traits>

template<typename T, int Dim>
class Unit {
    public:
    explicit Unit(T t): _value(t) {}

    template<int D = Dim, typename std::enable_if_t<D==0, int> = 0>
    operator T() { static_assert(Dim==0, ""); return _value; } //static_assert not necessary, but gives error if template is removed
    T _value;
};

template<typename S, typename T, int Dim>
auto operator*(S s, Unit<T,Dim> unit)
{
    return Unit<T, Dim>(s * unit._value);
}

auto main() -> int
{
    auto i = double{0};

//Scalar test
    auto scalar = int{0};
    auto x = Unit<double,1>(i);
    auto test = scalar * x;

//Conversion test
    auto y = Unit<double,0>(i);
    return y   i;
}

This works perfectly fine in clang (https://godbolt.org/z/8Pev7W6Y1). However, due to a GCC bug with templated conversion operators (Conversion operator: gcc vs clang), this does not work in GCC.

It is not possible to remove the SFINAE construction because it (correctly) runs into the static_assert.

Do you have an idea for equivalent code that also works in GCC? The code should work in C 17 with both compilers.

CodePudding user response:

You can use specialization instead of SFINAE. To avoid too much duplication you can move the common parts (anything that does not depend on Dim) to a base class:

#include <type_traits>

template <typename T>
class base_unit {
    public:
    explicit base_unit(T t): _value(t) {}
    T _value;
};


template<typename T, int Dim>
class Unit : public base_unit<T> {
public:
    explicit Unit(T t): base_unit<T>(t) {}
};

template <typename T>
class Unit<T,0> : public base_unit<T> {
public:
    explicit Unit(T t) : base_unit<T>(t) {}
    operator T() { return base_unit<T>::_value; }
};

template<typename S, typename T, int Dim>
auto operator*(S s, Unit<T,Dim> unit)
{
    return Unit<T, Dim>(s * unit._value);
}

auto main() -> int
{
    auto i = double{0};

//Scalar test
    auto scalar = int{0};
    auto x = Unit<double,1>(i);
    auto test = scalar * x;

//Conversion test
    auto y = Unit<double,0>(i);
    return y   i;
}

Live Demo

Note that this is a little old-fashioned and does not consider more modern C 20 approaches (for example the operator T() requires (Dim == 0) mentioned in a comment).

CodePudding user response:

Do you have an idea for equivalent code that also works in GCC? The code should work in C 17 with both compilers.

Since you don't want the code on the call side to be changed and the problem is with y i, you could overload operator as shown below:

#include <type_traits>
#include <iostream>
template<typename T, int Dim>
class Unit {
    public:
    explicit Unit(T t): _value(t) {}

    template<int D = Dim, typename std::enable_if_t<D==0, int> = 0>
    operator T() { static_assert(Dim==0, ""); return _value; } //static_assert not necessary, but gives error if template is removed
    T _value;
    //overload operator 
    template<typename U,int D, typename V>  friend U operator ( Unit<U,D>& u,  V& v);  
};
//define overloaded operator 
template<typename U, int D, typename V> U operator ( Unit<U,D>& u,  V&v)
{
    std::cout<<u.operator U()   v;//just for checking the value
    return u.operator U()   v;
}
template<typename S, typename T, int Dim>
auto operator*(S s, Unit<T,Dim> unit)
{
    return Unit<T, Dim>(s * unit._value);
}

auto main() -> int
{
    auto i = double{0};

//Scalar test
    auto scalar = int{0};
    auto x = Unit<double,1>(i);
    auto test = scalar * x;

//Conversion test
    auto y = Unit<double,0>(i);
    
    return y   i;
}

The output the above program can be seen here.

  • Related