头文件中指定模板的数组大小的c++模板推导

c++ template deduction of array size for specified template in header file

本文关键字:c++ 数组 文件      更新时间:2023-10-16

我正在读Meyers关于现代c++的书,在书中我发现一个代码片段可能很有用:

template <typename T, std::size_t N>
constexpr std::size_t arraySize(T (&) [N]) noexcept {
return N;
}

这个函数为我们推导N作为编译时间常数。所以我想把它应用到我的代码中:

template <typename T, std::size_t N>
constexpr std::size_t arraySize(T (&) [N]) noexcept {
return N;
}
template <typename T>
class A {
public:
const static char* names[];
};
template<typename T>
const char* A<T>::names[] = {"foo", "bar"};
template<>
const char* A<bool>::names[] = {"foo","bar", "foobar"};

如果放在一个文件中,它可以很好地工作,arraySize(A<int>::names)2arraySize(A<bool>::names)3

但当用于需要单独.h.cpp的大型项目时,问题来了:

  1. 如果将指定版本的A<bool>::names[]的声明放在.cpp中,代码会编译(并链接),但编译器在推导arraySize()时看不到它,因此arraySize(A<bool>::names)被推导为2

  2. 如果把A<bool>::names[]的声明放在.h中,当然,我们会得到一个"重复符号"链接错误。

那么如何将arraySize(A<bool>::names)正确地推导为3呢?

您使用的是constexpr函数,因此使用的是C++11(或更新版本)编译器。

因此,如果你的class A只包含names(否则你可以只为names创建一个基类),你可以声明它为static constexpr,(专门化类)你可以在body类中初始化它(在头中),并在body外定义它(如果需要的话,在cpp文件中),而不初始化它。

而且,如果我理解正确,从C++17开始,类定义的外部就不再是必要的了。

以下是的示例

#include <iostream>
template <typename T, std::size_t N>
constexpr std::size_t arraySize(T (&) [N]) noexcept {
return N;
}
template <typename T>
class A {
public:
static constexpr char const * names[] = {"foo", "bar"};
};
template <>
class A<bool> {
public:
static constexpr char const * names[] = {"foo", "bar", "foobar"};
};
template<typename T>
constexpr char const * A<T>::names[];
constexpr char const * A<bool>::names[];

int main()
{
std::cout << arraySize(A<long>::names) << std::endl; // print 2
std::cout << arraySize(A<bool>::names) << std::endl; // print 3
}

---编辑---

OP写入

对于只有一个成员的类来说,它很优雅。但我的类包含其他成员和方法,所以我会选择"完整的维度",因为这需要对我的原始代码进行最小的修改

我添加了一个修改后的示例,其中names插入到一个简单的模板基结构(namesB,名称的基)中,只包含names

这允许仅对简单的namesB进行专门化,并且仅对复杂的class A进行一次开发。

#include <iostream>
template <typename T, std::size_t N>
constexpr std::size_t arraySize(T (&) [N]) noexcept
{ return N; }
template <typename T>
struct namesB
{ static constexpr char const * names[] = {"foo", "bar"}; };
template <>
struct namesB<bool>
{ static constexpr char const * names[] = {"foo", "bar", "foobar"}; };
template <typename T>
class A : public namesB<T>
{ /* a complex class defined only one time */ };

template<typename T>
constexpr char const * namesB<T>::names[];
constexpr char const * namesB<bool>::names[];

int main()
{
std::cout << arraySize(A<long>::names) << std::endl; // print 2
std::cout << arraySize(A<bool>::names) << std::endl; // print 3
}

不能让类模板使用.cpp文件,它根本不适用于编译器的工作方式。编译器所做的是在每次向类发送新的数据类型时生成一个版本。

因此,基本上,如果你有一个类模板,它接受一个变量,并且你创建了两个类类型的对象,一个带有int,另一个带有string,编译器将对类进行两个定义。问题是编译器不知道如何使用.cpp来执行此操作。我听说您可以将.cpp重命名为其他任何东西,比如.tpp,或者手动重载类定义。这两种方法都有点麻烦,每个人都只是在.h文件中编写模板类,这可能与其他类不一样,但事实就是如此。