【问题标题】:C++ Template specializations for primary, templated, specialised and aliased template arguments主要、模板化、专用化和别名模板参数的 C++ 模板特化
【发布时间】:2020-12-07 23:14:49
【问题描述】:

我正在尝试专门化一个模板来处理主要类型、模板类型和专用/别名模板类型。请参阅下面的代码示例。

它编译但不链接。

如何为zero() 编写一个模板特化,我可以调用为zero<myvec>() 并且仍然可以调用zero<double> 等?

原因是在我的应用程序中,我没有给出myvec 的大小N,所以我不能写zero<vec_t, N>(),但是,我知道myvec 是一个模板别名,如下所示,我知道模板的结构等,而不是大小:

#include <iostream>
#include <array>
#include <vector>

template<std::size_t N>
using vec_t = std::array<double, N>;

using v_t = std::vector<double>;

using v5_t = vec_t<5>;

// generic declaration
template<typename T> T zero();

// full specialization to double 
template<> double zero() { std::cout << " -> Double\n"; return 0;}
// full specialization to v_t
template<> v_t zero() { std::cout << " -> vector<double>\n"; return v_t{}; };
// full specialization to v5_t
template<> v5_t zero() { std::cout << " -> vec_t<5>\n"; return v5_t{}; };

// attempt at partial specialization to vec_t<N>
template<template<typename T, std::size_t N> typename V, typename T, std::size_t N> V<T, N> zero() {
std::cout << " -> V<T, N>\n";
return V<T, N>{}; 
};
template<template<std::size_t N> typename V, std::size_t N> V<N> zero() {
std::cout << " -> V<N>\n";
return V<N>{};
};


int main() {

  double z1 = zero<double>(); // works

  v_t z2 = zero<v_t>(); // works

  v5_t z3 = zero<v5_t>(); // works

  const std::size_t N = 6;
  vec_t<N> z4 = zero<std::array, double, N >(); // works, but requires full specs of vec_t

  vec_t<N> z5 = zero<vec_t, N>(); // works, but requires N

  using myvec = vec_t<6>;
  myvec z6 = zero<myvec>(); // linker error ! but is what I'd like to write

  return 0;
}

你可以看看CompilerExplorer

非常感谢您的帮助! (P.S. 最高 Cpp17 的解决方案都可以)

【问题讨论】:

    标签: c++ templates c++17 template-meta-programming template-specialization


    【解决方案1】:

    您正在寻找偏特化,但对于函数,C++ 中不允许偏特化。

    但是对于类是允许的,所以,如果您可以将zero() 模板函数替换为模板类中的方法,您可以编写如下内容

    #include <iostream>
    #include <array>
    #include <vector>
    
    template<std::size_t N>
    using vec_t = std::array<double, N>;
    
    using v_t = std::vector<double>;
    
    using v5_t = vec_t<5>;
    
    // generic declaration
    template <typename T>
    struct zero;
    
    // specializations
    
    template <>
    struct zero<double>
     { double operator() () { std::cout << " -> double\n"; return {};} };
    
    template <>
    struct zero<v_t>
     { v_t operator() () { std::cout << " -> v_t\n"; return {};} };
    
    template <>
    struct zero<v5_t>
     { v5_t operator() () { std::cout << " -> v5_t\n"; return {};} };
    
    template <std::size_t N>
    struct zero<vec_t<N>>
     { vec_t<N> operator() () { std::cout << " -> vec_t + N\n"; return {};} };
    
    template <template <std::size_t> class C, std::size_t N>
    struct zero<C<N>>
     { C<N> operator() () { std::cout << " -> C + N\n"; return {};} };
    
    
    int main() {
    
      double z1 = zero<double>{}(); 
    
      v_t z2 = zero<v_t>{}();
    
      v5_t z3 = zero<v5_t>{}();
    
      constexpr std::size_t N = 6;
    
      vec_t<N> z4 = zero<std::array<double, N>>{}(); 
    
      vec_t<N> z5 = zero<vec_t<N>>{}();
    
      using myvec = vec_t<6>;
    
      myvec z6 = zero<myvec>{}(); // now works
    }
    

    注意不能再调用zero()如下

    zero<double>();
    

    因为你必须创建所需类型的对象,所以

    // .........VV
    zero<double>{}();
    

    如果这是一个问题,你可以使用静态方法,所以如果你可以给方法命名(比如func()),你可以如下调用它

    zero<double>::func();
    

    double 特化在哪里

    template <>
    struct zero<double>
     { static double func () { std::cout << " -> double\n"; return {};} };
    

    【讨论】:

    • 感谢您的回答。我之前也在考虑函数的部分专业化也是被禁止的问题。然而,函数的部分特化应该会导致编译时错误,不是吗?我的代码确实编译但没有链接。另外,请注意,我通过using aliases 进行“专业化”。据我了解,这对编译器是透明的。对我来说,从z4z 的转换已经是部分专业化——std:array、double 被替换为 vec_t。似乎应该可以多走一步,也可以摆脱 N。
    • @JReichardt - 你写的不是部分专业化:是重载。但是,那样的话,你不能随意调用函数(zero&lt;myvec&gt;),你必须调用zero&lt;std::array, double, 6u&gt;()。链接器错误来自 zero&lt;myvec&gt;() 匹配未定义的初始(通用)声明(因此编译)(因此链接错误)。
    • 似乎我对重载和专业化的确切区别感到困惑。你会说我重载了返回类型吗? Zero() 不接受任何参数。无论如何,你是对的 - 定义通用声明解决了问题 - 见下文。再次感谢。
    • 专业化和重载之间的区别在评论中并不容易解释......您的前三个专业化(doublev_tv5_t)是第一个声明的专业化,接收单个模板参数(类型名)。下面的zero()V&lt;T, N&gt;V&lt;N&gt;)是基于不同的模板参数,所以和原来的泛型声明不匹配,完全不相关。
    【解决方案2】:

    按照@max66 的回答,您可以保留原始 API,但将调用委托给模板结构:

    // generic declaration
    template <typename T>
    struct zero_s;
    
    // specializations
    template <>
    struct zero_s<double> {
        double operator() () {
            std::cout << " -> double\n"; return {};
        }
    };
    
    template <template <typename T, std::size_t> class C, typename T, std::size_t N>
    struct zero_s<C<T, N>> {
        C<Type, N> operator() () {
            std::cout << " -> C<T, Size>, Size = " << N << "\n";
            return {};
        }
    };
    
    template <std::size_t N> struct zero_s<vec_t<N>> {
        vec_t<N> operator() () {
            std::cout << " -> vec_t + N: " << N << "\n"; return {};
        }
    };
        
    // ...    
     
    //------------------------------------------------
    // generic "zero" - still a free function
    //------------------------------------------------
    template<typename T, typename... Ts> T zero() {
        return zero_s<T, Ts...>{}();
    }
    

    见:https://godbolt.org/z/cEcvqK

    【讨论】:

      【解决方案3】:

      再次感谢您提供的两个出色答案。我真的很喜欢可变参数模板解决方案;-)。 事实证明,有一个更简单的解决方案,虽然公认不太通用,解决了所陈述的问题,而不用结构替换函数。只需在 zero() 的泛型声明中添加定义,即更改

      template <typename T> T zero();
      

      template <typename T> T zero() {return {};};
      

      并且代码编译并运行得很好。对于我为什么要做这些愚蠢的事情,我有一点解释:在我的应用程序中,我将不同大小的 Eigen::Matrix、Eigen::Tensor 等类型的对象(通过模板别名实现)与主标量混合类型。不幸的是,Eigen 类型没有通过{} 进行标准的零初始化,而是具有静态 setZero() 函数。上述解决方案允许我使用 Eigen 特定初始化作为标准,然后完全专注于标量类型。

      我更新了CompilerExplorer

      【讨论】:

        猜你喜欢
        • 2017-01-20
        • 2017-09-10
        • 2022-07-05
        • 1970-01-01
        • 1970-01-01
        • 2015-02-19
        • 2020-07-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多