C++11 gcc、clang和msvc在变量模板示例中的不同结果-有人能解释吗?

C++11 gcc、clang和msvc在变量模板示例中的不同结果-有人能解释吗?,c++11,gcc,clang,variadic-templates,msvc12,C++11,Gcc,Clang,Variadic Templates,Msvc12,我需要创建一个函数,该函数接受一个带有可变参数和一些固定参数的函数指针,但无法在Visual Studio 2013上运行。我假设VisualStudio2013可能缺少了一些经常出现的情况,并制作了一个最小的示例,它可以满足我的需要,并针对gcc和clang进行了尝试。我在三个编译器上得到了完全不同的结果。所以我想解决的问题是: 我的例子真的有效吗?如果不是,我做错了什么 如果我的例子是有效的,那么关于gcc和clang行为的任何提示(让我们把msvc算出来,因为它是一个黑盒子) 例如: #i

我需要创建一个函数,该函数接受一个带有可变参数和一些固定参数的函数指针,但无法在Visual Studio 2013上运行。我假设VisualStudio2013可能缺少了一些经常出现的情况,并制作了一个最小的示例,它可以满足我的需要,并针对gcc和clang进行了尝试。我在三个编译器上得到了完全不同的结果。所以我想解决的问题是:

  • 我的例子真的有效吗?如果不是,我做错了什么
  • 如果我的例子是有效的,那么关于gcc和clang行为的任何提示(让我们把msvc算出来,因为它是一个黑盒子)
  • 例如:

    #include <iostream>
    
    struct foo
    {
        void work(int first, int second, int third)
        {
            std::cout << "0: " << first << ",1: " << second << ",2: " << third << std::endl;
        }
        void work_with_double(double first, int second, int third, int fourth)
        {
            std::cout << "0: " << first << ",1: " << second << ",2: " << third << ",3: " << fourth << std::endl;
        }
    };
    
    template<typename ... argument_types>
    void invoke_foo(foo* instance, int first, int second, int third, void (foo::*method)(argument_types ... arguments, int, int, int), argument_types ... arguments)
    {
        (instance->*method)(arguments ..., first, second, third);
    }
    
    int main(int argc, char** argv)
    {
        foo instance;
        invoke_foo(&instance, 1, 2, 3, &foo::work); // gcc ok, clang err, msvc 2013 err
        invoke_foo<>(&instance, 1, 2, 3, &foo::work); // gcc ok, clang err, msvc 2013 err
        invoke_foo(&instance, 1, 2, 3, &foo::work_with_double, 1.0); // gcc err, clang ok, msvc 2013 err
        invoke_foo<double>(&instance, 1, 2, 3, &foo::work_with_double, 1.0); // gcc err, clang err, msvc 2013 ok
        return 0;
    }
    

    每种情况下的问题都是编译器试图从
    方法
    参数推断
    参数类型
    ,这是非法的,因为可变模板参数只能在参数列表末尾推断

    void (foo::*method)(argument_types ... arguments, int, int, int)
                        ^^^^^^^^^^^^^^^^^^ can't infer here
                                                    ^^^^^^^^^^^^^^^ because of these
    
    解决方法是使用类似于
    identity
    的帮助程序,保护
    参数类型
    在此上下文中不被推断:

    template<class T> struct identity { using type = T; };
    template<class T> using identity_t = typename identity<T>::type;
    
    // ...
    
    template<typename ... argument_types>
    void invoke_foo(foo* instance, int first, int second, int third,
        void (foo::*method)(identity_t<argument_types> ... arguments, int, int, int), argument_types ... arguments)
    //                      ^^^^^^^^^^^ fix here
    
    模板结构标识{using type=T;};
    使用identity的模板\u t=typename identity::type;
    // ...
    模板
    void invoke_foo(foo*实例,int-first,int-second,int-third,
    void(foo::*方法)(identity\u t,表示:

    5-非推断上下文为:[……]

    • 不在参数声明列表末尾出现的函数参数包
    6-当以包含非推断上下文的方式指定类型名时,构成该类型名的所有类型也都是非推断的。但是,复合类型可以同时包含推断类型和非推断类型

    这里,
    参数类型
    在推断
    方法
    的类型时处于非推断上下文中,但在推断
    invoke\u foo
    的尾部参数的类型时处于推断上下文中

    你可以测试的另一个编译器是ICC(英特尔C++编译器);ICC拒绝前两种形式,接受后两种形式,与gcc完全相反。编译器行为如此不同的原因在于,处理此类代码本质上是一个错误处理问题,特别是识别模板参数何时出现在非推断上下文中,并使用推断出的类型相反,编译器(各自以自己的方式)认识到

    参数类型
    不能在
    方法
    中推导,但未能意识到或接受它可以在其他地方推导

    具体而言,似乎:

    • gcc假设如果它不能从
      方法
      推断
      参数类型
      ,则它必须为空
    • clang假设如果
      参数类型
      被推断为空或显式指定,这一定是一个错误
    • MSVC无法让
      参数类型的推断覆盖推断失败,但如果明确指定,则可以
    • ICC假设如果
      参数类型
      被推断为空,则这一定是一个错误

    您可以在该函数中显式地创建
    参数类型
    signature@PiotrSkotnicki谢谢,这使它能在msvc上工作,这正是我所需要的。但我很想看看answer@PiotrSkotnicki我也是——老实说,我希望至少gcc和clang的表现是一样的。@ecatmur,我想这就是原因,但不幸的是我我向其传递指针的接口方法是以这样一种方式创建的,即公共包位于末尾:(但是,由于我明确声明这里有一个模式,在这种情况下,
    int,int,int
    位于变量参数包的末尾,是什么使得无法推断包本身(我想我只是没有看到什么)?如果你能解释一下,我将不胜感激。@Rudolfsfbundulis更新-希望我已经回答了你的问题。是的,谢谢你的努力,这让我更清楚了:)仅供参考-msvc 2015因修复程序崩溃:D@RudolfsBundulis嗯,它适用于我(版本19.00.23506)和在线(版本19.00.23720.0)。你会遇到什么崩溃?
    template<class T> struct identity { using type = T; };
    template<class T> using identity_t = typename identity<T>::type;
    
    // ...
    
    template<typename ... argument_types>
    void invoke_foo(foo* instance, int first, int second, int third,
        void (foo::*method)(identity_t<argument_types> ... arguments, int, int, int), argument_types ... arguments)
    //                      ^^^^^^^^^^^ fix here