【问题标题】:Different versions of g++ have inconsistent result of overload resolution不同版本的g++重载解析结果不一致
【发布时间】:2021-10-05 15:34:41
【问题描述】:

当我使用 g++ 5.4.0 时,下面的示例代码按预期工作,但是在我将 g++ 更新到 10.2.0 后,结果发生了变化。 我也在clang++ 11.0.1上测试了示例代码,结果和g++ 5.4.0一样。

我已经搜索了一些相关问题,但没有得到有效的答案。 据我所知,重载函数应该在模板之前匹配, 为什么 g++ 10.2.0 会得到不同的结果,我该如何解决?

因为原来的源代码很复杂,用其他c++特性来重构并不容易,这个问题能不能稍微改动一下?

示例代码的目标是使用重载函数Base::operator const std::string&()执行一些特殊动作,并使用模板函数执行普通动作。

#include <string>
#include <iostream>

class Base 
{
public:
    template <class T>
    operator const T&() const;
    virtual operator const std::string&() const;
};

template <class T>
Base::operator const T&() const
{
    std::cout << "use template method" << std::endl;
    static T tmp{};
    return tmp;
}

Base::operator const std::string&() const
{
    std::cout << "use overload method" << std::endl;
    const static std::string tmp;
    return tmp;
}

template <class T>
class Derive : public Base
{
public:
    operator const T&() const
    {
        const T& res = Base::operator const T&();
        return res;
    }
};

int main()
{
    Derive<std::string> a;
    const std::string& b = a;
    return 1;
}

g++ 5.4.0 结果:

g++ -std=c++11  main.cpp -o test && ./test
use overload method

g++ 10.2.0 结果:

g++ -std=c++11 main.cpp -o test && ./test          
use template method

clang++ 11.0.1 结果:

clang++ -std=c++11  main.cpp -o test && ./test
use overload method

【问题讨论】:

    标签: c++ templates g++ language-lawyer overload-resolution


    【解决方案1】:

    可能的解决方法:

    #include <string>
    #include <iostream>
    
    class Base 
    {
    public:
        virtual operator const std::string&() const
        {
        std::cout << "use overload method" << std::endl;
        const static std::string tmp;
        return tmp;
        }
    
        template<typename T>
        operator const T&() const
        {
        std::cout << "use template method" << std::endl;
        static T tmp{};
        return tmp;
        }
    };
    
    
    template <class T>
    class Derive : public Base
    {
    public:
        template<typename U = T>   // modification: let operator be template
        operator const T&() const
        {
            std::cout << "called Derive::operator T" << std::endl;   
            const T& res = Base::operator const U&();   // modification: call type operator on template type parameter
            return res;
        }
    };
    
    int main()
    {
        Derive<std::string> a;
        const std::string& b = a;
    
        Derive<int> i;
        const int& ri = i;
        return 0;
    }
    

    通过 godbolt.org x86_64 gcc 6.3 -std=c++11 -O0 验证

    输出: ASM 生成编译器返回:0 执行构建编译器返回:0 返回的程序:0 使用重载方法 称为 Derive::operator T 使用模板方法

    【讨论】:

    • 感谢您的建议,但似乎不会调用 Derive::operator const T&() (godbolt.org/z/59xv4szxc)
    • 在模板特化 Derive<:string> 的情况下,没有机会(也在原始解决方案中)可以调用模板成员 Derive::operator T()。有一个派生的(来自 Base)公共虚拟成员运算符,它比模板成员运算符更好 Derive<:string>::operator const std::string&();
    【解决方案2】:

    这绝对是一个 GCC 错误:

    template <class T>
    class Derive : public Base {
     public:
      operator const T&() const override {
        using Y = std::string;
        static_assert(std::is_same<T, Y>::value, "");
        
        std::string static res;
    
        res = Base::operator const Y&();
        res = Base::operator const T&();
        return res;
      }
    };
    

    这里,即使YT 相同,也会调用两个不同版本的运算符。 Godbolt

    Clang 具有正确的行为,您可以将其用作解决方法。请报告该错误,以便在 GCC 的后续版本中对其进行修复。

    【讨论】:

    • 它已经存在了很长一段时间,我对 Godbolt 做了一个快速的二分法,似乎第一个介绍它的是 8.1
    • @AyxanHaqverdili 谢谢,我可以在模板函数上添加一个像 enable_if 这样的条件来避免编译器首先使用模板吗?
    • @sizzle 好像不行gcc.godbolt.org/z/8n6xEfvqx
    • @sizzle 我确实检查过了,在这种情况下编译器似乎完全忽略了非模板化重载。
    猜你喜欢
    • 2013-07-16
    • 1970-01-01
    • 1970-01-01
    • 2015-09-14
    • 2019-09-05
    • 2012-07-26
    • 2016-01-08
    • 2012-06-14
    • 1970-01-01
    相关资源
    最近更新 更多