【问题标题】:GCC 7.X C++17 template auto parameter type deduction with enum class bugGCC 7.X C++17 模板自动参数类型推导与枚举类错误
【发布时间】:2021-03-19 02:05:08
【问题描述】:

我注意到 GCC 7.X 中可能存在一个错误,但我在任何地方都找不到任何识别此错误的东西。很高兴知道这是否曾被报告为编译器错误或被隐藏,在您了解它之前,您会假设它可以正常工作。特别是该功能被列为支持此版本。

到目前为止,我使用此检查来确定是否可以使用模板自动参数。

#if (__cpp_template_auto >= 201606L) || (__cpp_nontype_template_parameter_auto >= 201606L)
    #define TEMPLATE_AUTO_AVAILABLE 1
#endif

在旧代码中,您必须像这样声明模板:

template <typename EnumType, EnumType enumValue>
auto func() { exclusive version for EnumType::enumValue }

然后这样称呼它:

auto value = func<MyEnumType, MyEnumType::value>();

使用自动模板参数很容易像这样使用它:

template <auto enumValue>
auto func() { exclusive version for decltype(enumValue)::enumValue }
auto value = func<MyEnumType::value>();

问题在于它在 GCC 7.X 中不是等效的,并且可能导致严重的错误。

最小示例代码:

enum class FirstEnum : short { firstVal = 7 };
enum class SecondEnum : size_t { secondVal = 7 };

template <typename T>
struct TypeName;

template <>
struct TypeName<FirstEnum>
{
    static constexpr auto name = "FirstEnum";
};

template <>
struct TypeName<SecondEnum>
{
    static constexpr auto name = "SecondEnum";
};


template <auto t>
void func()
{
    std::cout << TypeName<decltype(t)>::name << std::endl;
}

int main()
{
    func<FirstEnum::firstVal>();
    func<SecondEnum::secondVal>();
}

【问题讨论】:

    标签: c++ templates gcc enumeration auto


    【解决方案1】:

    似乎模板被实例化为第一个整数值而不是实际的模板参数。因此,它不按类型区分枚举类,并为与模板一起使用的第一个枚举类实例化单个模板。以下所有具有不同枚举类型的使用都将与第一个实例化版本相关联。它也不尊重枚举类的底层类型。

    Godbolt 示例:https://godbolt.org/z/8P9vEn

    在稍微复杂一点的例子中,您会注意到模板实际上不是按枚举类类型和值生成的,而是按其他类型生成的。这件事我无法理解。您可以看到 decltype(t) 在模板参数列表和模板函数本身中是不同的,而且奇怪的是,它不依赖于“t”的类型,而是函数参数类型。好吧,这个答案导致的问题比它真正回答的要多。 https://godbolt.org/z/sorbMr

    GCC 7.X 不是 GCC 的最新版本,但仍然很常用,尤其是在嵌入式解决方案中。避免此类已知错误在许多应用程序中非常重要,显然最好了解这些问题。如果您了解它是如何被破坏或如何解决此问题并且仍然能够使用 auto 参数推断枚举类类型,请随时添加更多信息。

    如果以后godbolt链接不起作用,这里是扩展示例:

    #include <type_traits>
    #include <typeinfo>
    #include <iostream>
    #include <memory>
    #include <string>
    #include <cxxabi.h>
    #include <limits>
    
    inline std::string demangle(const char* name)
    {
        int status = -1;
        std::unique_ptr<char, void(*)(void*)> res {
            abi::__cxa_demangle(name, nullptr, nullptr, &status),
            std::free
        };
        return (status == 0) ? res.get() : name;
    }
    
    template<typename T>
    std::string fullTypeName()
    {
        return demangle(typeid(T).name());
    }
    
    template <typename EnumType>
    constexpr auto extractEnum(EnumType val)
    {
        return static_cast<std::underlying_type_t<std::remove_reference_t<EnumType>>>(val);
    }
    
    enum class FirstEnum : unsigned char { val = 0 };
    enum class SecondEnum : size_t { val = 0 };
    enum class ThirdEnum : int { val = 0 };
    
    template <auto t, typename T = decltype(t)>
    void func(T param)
    {
        std::cout << fullTypeName<decltype(t)>()
            << "/" << fullTypeName<T>()
            << " size T: " << sizeof(decltype(t))
            << " size t: " << sizeof(T)
            << " value: " << param << std::endl;
    }
    
    int main()
    {
        func<FirstEnum::val>(0);
        func<FirstEnum::val>(0U);
        func<SecondEnum::val>(500);
        func<SecondEnum::val>(50000000000000000);
        func<ThirdEnum::val>(-1);
    }
    

    GCC 7.X 的结果:

    FirstEnum/int size T: 1 size t: 4 value: 0
    FirstEnum/unsigned int size T: 1 size t: 4 value: 0
    FirstEnum/int size T: 1 size t: 4 value: 500
    SecondEnum/long size T: 8 size t: 8 value: 50000000000000000
    FirstEnum/int size T: 1 size t: 4 value: -1
    

    GCC 8.1+ 或 Clang 的结果:

    FirstEnum/int size T: 1 size t: 4 value: 0
    FirstEnum/unsigned int size T: 1 size t: 4 value: 0
    SecondEnum/int size T: 8 size t: 4 value: 500
    SecondEnum/long size T: 8 size t: 8 value: 50000000000000000
    ThirdEnum/int size T: 4 size t: 4 value: -1
    

    【讨论】:

      【解决方案2】:

      只要不声明T 类型的任何参数,您就可以使用此解决方法

      template <auto t, typename T = decltype(t)>
      void func()
      {
          std::cout << TypeName<decltype(t)>::name << std::endl;
      }
      

      这样Tt 的实际类型,并强制模板单独实例化。 https://godbolt.org/z/Y967Ps

      如果您声明T 类型的参数,此解决方案将不起作用,因为它将从函数参数类型而不是默认模板类型参数推导出来。

      我将此作为单独的答案发布,以防您正在寻找解决方法,而不是解释。这样您就可以投票选出您认为对其他人更有用的答案。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-03-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-02-15
        相关资源
        最近更新 更多