Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bug in templated conversion operator in GCC: Workaround?

Tags:

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.

like image 505
Henk Avatar asked Nov 30 '21 13:11

Henk


1 Answers

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).

like image 77
463035818_is_not_a_number Avatar answered Sep 30 '22 18:09

463035818_is_not_a_number