GCC 中模板化转换运算符中的错误:解决方法?

Bug in templated conversion operator in GCC: Workaround?

我想要一个 class 代表具有某种维度的单位。这应该表达类似 1.5m^2 的内容。应允许与某种类型进行标量乘法,无量纲单位的行为应与基础类型完全相同。这是我的解决方案:

#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;
}

这在 clang (https://godbolt.org/z/8Pev7W6Y1). However, due to a GCC bug with templated conversion operators () 中工作得很好,在 GCC 中不起作用。

无法删除 SFINAE 结构,因为它(正确地)遇到了 static_assert

您是否有同样适用于 GCC 的等效代码的想法?代码应该在 C++17 中与两个编译器一起工作。

您可以使用专业化而不是 SFINAE。为避免太多重复,您可以将公共部分(任何不依赖于 Dim 的部分)移动到基础 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

请注意,这有点过时并且没有考虑更现代的 C++20 方法(例如评论中提到的 operator T() requires (Dim == 0))。

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

由于您不想更改调用端的代码并且问题出在 y+i,您可以重载 operator+,如下所示:

#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;
}

上面程序的输出可见here.