代码之家  ›  专栏  ›  技术社区  ›  PaperBirdMaster

具有模板参数推断和默认模板参数的模板变量

  •  9
  • PaperBirdMaster  · 技术社区  · 6 年前

    被一个 similar question 我试着以标准中提到的问题为例:

    template <typename T, typename U = int> struct S;
    template <typename T = int, typename U> struct S
    { void f() { std::cout << __PRETTY_FUNCTION__ << '\n'; } };
    
    int main()
    {
        S s; s.f();
        return 0;
    }
    

    上面的代码打印出来 void S<int, int>::f() [T = int, U = int] compiled with gcc HEAD 8.0.1 201803 但无法使用编译 clang HEAD 7.0.0 除非在实例化过程中使用尖括号:

    S s; s.f(); // error: declaration of variable 's' with deduced type 'S' requires an initializer
    S<> t; t.f(); // Correct
    

    撇开这个问题不谈,我已经检查了另一个 模板风格 对于这种特殊行为,代码以非常不规则的方式被接受或拒绝:

    Template function
    template <typename T, typename U = int> void function();
    template <typename T = int, typename U> void function()
    { std::cout << __PRETTY_FUNCTION__ << '\n'; }
    
    int main()
    {
        /* Rejected by GCC: no matching function for call to 'function()'
           template argument deduction/substitution failed:
           couldn't deduce template parameter 'T'
           same error with function<>()
    
           CLang compiles without issues */
        function(); // CLang prints 'void function() [T = int, U = int]'
        return 0;
    }
    
    Template variable
    template <typename T, typename U = int> int variable;
    template <typename T = int, typename U> int variable = 0;
    
    int main()
    {
        /* GCC complains about wrong number of template arguments (0, should be at least 1)
         while CLang complains about redefinition of 'variable' */
        std::cout << variable<> << '\n';
        return 0;
    }
    
    Template alias
    template <typename T, typename U = int> using alias = int;
    template <typename T = int, typename U> using alias = int;
    
    int main()
    {
        /* GCC complains about redefinition of 'alias'
           while CLang compiles just fine. */
        alias<> v = 0;
        std::cout << v << '\n';
        return 0;
    }
    

    这个 standards text 关于这个特性,没有区分不同的模板类型,所以我认为它们的行为应该相同。

    但是,两个编译器都拒绝使用模板变量,因此我对模板变量选项有一些疑问。对我来说,CLang拒绝模板变量抱怨重新定义是正确的,而GCC拒绝 错误的原因 ,但这一推理并没有遵循[温度参数]/10中的标准。

    那么对于模板变量,我应该期望什么呢

    • 由于重新定义,代码被拒绝(CLang是正确的)。
    • 接受代码,合并两个模板定义(GCC和CLang都是错误的)。
    2 回复  |  直到 6 年前
        1
  •  3
  •   Barry    6 年前

    对于类模板参数推断,这是一个巨大的错误。从…起 [temp.param]/14 :

    默认值集 模板参数 可供使用的方法是,以默认函数参数为([dcl.fct.default])的相同方式合并模板所有先前声明中的默认参数。 [示例:

    template<class T1, class T2 = int> class A;
    template<class T1 = int, class T2> class A;
    

    相当于

    template<class T1 = int, class T2 = int> class A;
    

    结束示例]

    当你写作时 S s ,这两个模板参数都是默认的,因此 rewrite 默认构造函数的类型为:

    template <typename T=int, typename U=int>
    S<T, U> __f();
    

    这是可行的,应该推断 S<int, int>


    对于函数,不需要指定 <> 如果可以推导出所有模板参数,则调用函数模板。这是 [temp.arg.explicit]/3 :

    如果可以推导出所有模板参数,则可以省略所有模板参数;在本例中,空模板参数列表 <> 本身也可以省略。

    请注意,这适用于扣减。对于别名模板或变量模板,没有任何推断。因此,不能忽略 <> 。这是 [temp.arg]/4 :

    模板参数打包或默认时 模板参数 使用,a 模板参数 列表可以为空。在这种情况下 <> 支架仍应用作 模板参数列表

        2
  •  1
  •   Peter K    6 年前

    免责声明:以下内容在C++14的上下文中有效。对于C++17,两种编译器都是错误的。 请参阅Barry的另一个答案。

    深入研究细节,我发现这里的Clang是正确的,而GCC是困惑的。

    • 第一种情况下,类模板(与函数模板不同)确实需要 <>

    • 第二种情况是函数模板,与第一种情况完全一样,由Clang处理,不需要使用语法要求 <> 指示使用了模板。这在C++中适用于所有上下文中的函数模板。

    • 第三种情况:关于变量,我看到Clang是正确的,而GCC是混乱的。如果重新声明变量 extern 叮当声接受了。

      template <typename T, typename U = int> int variable = 0;
      template <typename T = int, typename U> extern int variable;
      
      int main()
      {
          // accepted by clang++-3.9 -std=c++14
          std::cout << variable<> << '\n';
          return 0;
      }
      

      因此,它再次与标准和以前的情况保持一致。没有 外部人员 这是重新定义,是禁止的。

    • 第四种情况, using 样板叮当声再次表现出一致性。我使用typeid确保别名确实是int:

      template <typename T, typename U = int> using alias = int;
      template <typename T = int, typename U> using alias = int;
      
      int main()
      {
          alias<> v = 0;
          std::cout << v << '\n';
          std::cout << typeid(v).name() << '\n';
          return 0;
      }
      

      然后

      $ ./a.out | c++filt -t
      

      输出

      0
      int
      

    因此,正如 standard