【问题标题】:Bug in templated conversion operator in GCC: Workaround?GCC 中模板化转换运算符中的错误:解决方法?
【发布时间】:2022-01-07 05:21:12
【问题描述】:

我想要一个代表具有某种维度的单元的类。这应该表示 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) 中运行良好。但是,由于模板化转换运算符 (Conversion operator: gcc vs clang) 的 GCC 错误,这在 GCC 中不起作用。

无法删除 SFINAE 构造,因为它(正确地)运行到 static_assert

您对在 GCC 中也适用的等效代码有什么想法吗?代码应该在 C++17 中与两个编译器一起工作。

【问题讨论】:

  • 您能否展示一个您想要编译的代码示例,但如果模板被删除,它会无法编译?如果我从 Godbolt 链接中删除模板,gcc 和 clang 都会编译链接中的代码
  • 在 C++20 中,operator T() requires (Dim == 0) 可以完成 Demo 的工作。
  • @NathanOliver:我认为恰恰相反,static_cast&lt;double&gt;(Unit&lt;double,2&gt;(0)) 在没有 SFINAE(和 static_assert)的情况下会错误编译
  • @Jarod42 我明白了,但是如果他们保留静态断言,它仍然无法编译。我试图理解无法删除 SFINAE 构造,因为它(正确地)运行到了 static_assert。我不确定这是什么意思
  • 你说得对,我搞砸了最小的例子(并且失败并检查)。我的真实示例中的问题是转换运算符在给定的operator* 之前尝试,然后运行到static_assert。我正在尝试修复示例。

标签: c++ gcc implicit-conversion sfinae


【解决方案1】:

您可以使用专业化而不是 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;
}

Live Demo

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

【讨论】:

    【解决方案2】:

    您对在 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;
    }
    

    上面程序的输出可见here

    【讨论】:

    • 我认为 OP 希望隐式转换为 T (显式调用转换运算符无济于事)。我的意思是“陈述显而易见”是好的,但我不确定这是否是 OP 所说的“等效代码”
    • 是的,调用端不应该改代码。
    • @463035818_is_not_a_number 但是OP在他/她的问题中没有提到这个重要的事情。
    • 不确定你是否明白我的意思。 OPs 转换运算符是隐式的。虽然您的答案是一种解决方法,但它不适用于隐式转换。
    • @AnoopRana 实际上 OP 做到了。他们说你对在 GCC 中也有效的等效代码有什么想法吗 我不想更改驱动程序代码,只更改它调用的代码
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-06
    • 2021-05-15
    • 2020-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-21
    相关资源
    最近更新 更多