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

5

我希望有一个表示具有某种维度单位的类。它应该能够表示1.5平方米这样的数值。支持与某些类型进行标量乘法计算,并且无单位的数值应该与基础类型完全相同。这是我的解决方案:

#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)。然而,由于GCC存在模板转换操作符的错误 (Conversion operator: gcc vs clang), 所以在GCC中不起作用。
无法删除SFINAE结构,因为它(正确地)遇到了static_assert
你有没有关于等效代码的想法?该代码应该在C++17下使用两个编译器都可以工作。

您能展示一下您想要编译的代码示例吗?如果没有模板,它会无法编译通过吗?如果我从Godbolt链接中移除模板,那么gcc和clang都可以编译该链接中的代码。 - NathanOliver
在C++20中,operator T() requires (Dim == 0)可以完成这项工作演示 - Jarod42
@NathanOliver:我认为相反的情况是static_cast<double>(Unit<double,2>(0))如果没有SFINAE(和static_assert),将会编译错误。 - Jarod42
@Jarod42 我明白,但如果他们保留静态断言,它仍将无法编译。我试图理解不可能删除SFINAE结构,因为它(正确地)遇到了静态断言。我不确定这是什么意思。 - NathanOliver
你说得对,我搞砸了最小示例(并且没有检查)。在我的真实示例中,问题在于在给定的 operator* 之前尝试转换运算符,然后遇到 static_assert。我正在尝试修复这个示例。 - Henk
不确定它是否适用于这个使用案例,但Boost Units可能会引起兴趣。 - Eljay
2个回答

4

您可以使用特化来替代SFINAE。为了避免过多的重复,您可以将共同部分(任何不依赖于Dim的内容)移至基类中:

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

现场演示

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


0
你是否有在GCC下同样适用的等效代码想法?该代码应该在C++17下使用两个编译器都可以工作。
由于您不想更改调用端上的代码,并且问题出在 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;
}

上述程序的输出可以在这里看到。


2
我认为 OP 希望将隐式转换为 T(即明确调用转换运算符并没有帮助)。我的意思是“说出显而易见的事情”很好,但我不确定这是否是 OP 所谓的“等效代码”。 - 463035818_is_not_a_number
1
是的,调用方不应更改代码。 - Henk
@463035818 不是一个数字,但是OP在他/她的问题中没有提到这个重要的事情。 - Jason
不确定你是否理解我的意思。OP的转换运算符是隐式的。虽然你的答案是一个解决方法,但它并不适用于隐式转换。 - 463035818_is_not_a_number
@AnoopRana 实际上,原帖的作者已经这么说了。他们说“你有没有一个等价的代码想法,也能在GCC中运行”,对我来说这意味着我不想改变驱动程序代码,只想改变它调用的代码。 - NathanOliver
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接