代码之家  ›  专栏  ›  技术社区  ›  Charles L Wilcox

GCC C++14/17成员函数指针模板参数的差异

  •  1
  • Charles L Wilcox  · 技术社区  · 4 年前

    我有一些示例代码,在GCC/Clang/MSVC上用C++14编译,在Clang/MSVC上用C++17编译,在gcc8上用C++17编译。从x到10.1,它会产生一个错误。

    #include <vector> // vector
    
    template< typename Seq,
              typename Seq::value_type& ( Seq::*next )(),
              void ( Seq::*pop )() >
    void f( Seq& );
    
    template< typename Seq >
    void g( Seq& seq )
    {
        f< Seq, &Seq::back, &Seq::pop_back >( seq );
    }
    
    void foo()
    {
        std::vector< int > v;
        g( v );
    }
    

    我从GCC 10.1中收到以下错误,使用 CXXFLAGS=-std=c++17 :

    <source>: In instantiation of 'void g(Seq&) [with Seq = std::vector<int>]':
    <source>:17:10:   required from here
    <source>:11:41: error: no matching function for call to 'f<std::vector<int, std::allocator<int> >, (& std::vector<int, std::allocator<int> >::back), &std::vector<int, std::allocator<int> >::pop_back>(std::vector<int>&)'
       11 |     f< Seq, &Seq::back, &Seq::pop_back >( seq );
          |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~
    <source>:6:6: note: candidate: 'template<class Seq, typename Seq::value_type& (Seq::* next)(), void (Seq::* pop)()> void f(Seq&)'
        6 | void f( Seq& );
          |      ^
    <source>:6:6: note:   template argument deduction/substitution failed:
    <source>:11:41: error: 'int& (std::vector<int>::*)(){((int& (std::vector<int>::*)())std::vector<int>::back), 0}' is not a valid template argument for type 'int& (std::vector<int>::*)()'
       11 |     f< Seq, &Seq::back, &Seq::pop_back >( seq );
          |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~
    <source>:11:41: note: it must be a pointer-to-member of the form '&X::Y'
    Compiler returned: 1
    

    Compiler Explorer

    &Seq::back f ,但我收到几乎相同的错误。

    所以,基本问题是,这是无效的C++17代码,还是一个GCC错误?假设它是无效的C++17,我如何使它有效?

    'int& (std::vector<int>::*)(){((int& (std::vector<int>::*)())std::vector<int>::back), 0}' ? 我对这件事感到非常惊讶 { } 配对,和 0 .我知道外部部分是方法签名,而内部第一部分是将重载方法强制转换为预期签名,但是为什么 { } 0 ? 初始化列表?结构?成员指针的内部结构?

    0 回复  |  直到 4 年前
        1
  •  4
  •   HTNW    4 年前

    [namespace.std]/6

    ... [T]如果一个C++程序的行为是不明确的(可能是不正确的)…形成指向指定标准库或非静态成员函数的成员的指针( [member.functions] )或标准库成员函数模板的实例化。

    这基本上是为了允许实现者向标准函数添加默认模板参数和默认参数以及所有其他东西,这将使它们的函数指针和引用的类型与标准所说的接口不一致。此外,实现可以改变那些本身不太隐藏的部分,您不应该依赖它们。因此全面禁止(例外情况) 可寻址函数 iostream iomanip

    #include <vector>
    
    // or just take functors
    template<typename Seq, typename Seq::value_type &(*next)(Seq&), void (*pop)(Seq&)>
    void f(Seq&);
    
    template<typename Seq>
    void g(Seq &seq) {
        constexpr auto back = +[](Seq &s) -> typename Seq::value_type& { return s.back(); };
        constexpr auto pop_back = +[](Seq &s) -> void { s.pop_back(); };
        f<Seq, back, pop_back>(seq);
    }
    
    int main() {
        std::vector<int> v;
        g(v);
    }
    

    请注意,尽管这个显式规则在C++20中是新的,但在这之前就已经允许实现者修改函数的“标准”表示形式。也就是说,即使以前的C++版本没有这个子句,也有代码 不会是可移植的,因为不同的实现可能不会编译它。

    现在,即使有这样的津贴,我也不认为GCC已经脱离困境。首先,上面的代码无法用GCC编译 due to a bug .好的,很好,很简单(虽然冗长)的修复方法:提升 back pop_back g (例如,进入一个 namespace detail 默认情况下,Clang和GCC都使用GCC的标准库 这是一个非常奇怪的默认设置(Clang只在 -stdlib=libc++ ),但它告诉我们:不知何故,GCC未能编译Clang认为完美的东西。从技术上讲,他们都遵守标准;无法保证您的代码能够正常工作。但我们人类知道有些事情发生了。我们可以把这个问题简化为

    struct F { void foo() noexcept; }; // std::vector<int>::back is noexcept in libstdc++
    constexpr inline void (F::*ptr)() = &F::foo;
    template<void (F::*f)()> void g();
    int main() { g<ptr>(); }
    // Clang is happy, GCC is not
    

    我将把不可靠的错误消息标记为“GCC的实现细节正在泄漏”,否则错误中的表达式就没有多大意义。也许GCC在函数指针旁边保留了一些额外的标志或其他东西(我知道,例如指向 virtual 成员函数导致指向成员函数的指针的表示形式奇怪)。无论如何,我相信这次拒绝是另一个GCC错误。“指向”的指针- noexcept -“成员函数到指向成员函数的指针” conversion &F::foo constexpr 还有一个不是- 不例外 .

    TL;DR:你的代码是坏的,但GCC可能甚至是坏的 更多

        2
  •  0
  •   Charles L Wilcox    4 年前

    [res.on.exception]/5 [conv.fctptr]/1

    #include <vector> // vector
    
    template< typename Seq,
              typename Seq::value_type& ( Seq::*peek )(),
              void ( Seq::*pop )() >
    void f( Seq& );
    #if defined( __GNUC__ ) && !defined( __clang__ )
    template< typename Seq,
              typename Seq::value_type& ( Seq::*peek )() noexcept,
              void ( Seq::*pop )() >
    void f( Seq& );
    #endif // defined( __GNUC__ ) && !defined( __clang__ )
    
    template< typename Seq >
    void g( Seq& seq )
    {
        f< Seq, &Seq::back, &Seq::pop_back >( seq );
    }
    
    void foo()
    {
        std::vector< int > v;
        g( v );
    }
    

    Compiler Explorer