主要、模板化、专门化和别名模板参数的 C++ 模板特化
C++ Template specializations for primary, templated, specialised and aliased template arguments
我正在尝试专门化一个模板以同时处理主要类型、模板化类型和 specialised/aliased 模板化类型。请参阅下面的代码示例。
编译但不编译link。
我如何为 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++ 中,对于函数,部分特化是不允许的。
但是对于 类 是允许的,所以,如果可以接受将 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 {};} };
按照@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...>{}();
}
再次感谢两位出色的回答。我真的很喜欢可变参数模板解决方案 ;-)。
事实证明,有一个更简单的解决方案,虽然公认的不太通用,但无需用结构替换函数即可解决上述问题。只需在 zero() 的通用声明中添加一个定义,即 change
template <typename T> T zero();
到
template <typename T> T zero() {return {};};
并且代码可以正常编译和运行。对于我为什么做这些愚蠢的事情,我觉得有点解释是有道理的:在我的应用程序中,我将 Eigen::Matrix、Eigen::Tensor 等类型的对象与不同大小(通过模板别名实现)混合在一起主要标量类型。不幸的是,Eigen 类型没有通过 {}
进行标准的零初始化,而是具有静态 setZero() 函数。上面的解决方案允许我使用 Eigen 特定的初始化作为标准,然后完全专注于标量类型。
我更新了 CompilerExplorer
我正在尝试专门化一个模板以同时处理主要类型、模板化类型和 specialised/aliased 模板化类型。请参阅下面的代码示例。
编译但不编译link。
我如何为 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++ 中,对于函数,部分特化是不允许的。
但是对于 类 是允许的,所以,如果可以接受将 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 {};} };
按照@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...>{}();
}
再次感谢两位出色的回答。我真的很喜欢可变参数模板解决方案 ;-)。 事实证明,有一个更简单的解决方案,虽然公认的不太通用,但无需用结构替换函数即可解决上述问题。只需在 zero() 的通用声明中添加一个定义,即 change
template <typename T> T zero();
到
template <typename T> T zero() {return {};};
并且代码可以正常编译和运行。对于我为什么做这些愚蠢的事情,我觉得有点解释是有道理的:在我的应用程序中,我将 Eigen::Matrix、Eigen::Tensor 等类型的对象与不同大小(通过模板别名实现)混合在一起主要标量类型。不幸的是,Eigen 类型没有通过 {}
进行标准的零初始化,而是具有静态 setZero() 函数。上面的解决方案允许我使用 Eigen 特定的初始化作为标准,然后完全专注于标量类型。
我更新了 CompilerExplorer