PAYATHON 2026

如何在编译期禁止使用 std::string 实例化类模板?

技术老齐

先去掉 T 的引用和 cv 限定,再判断得到的类型是否为 std::string。C++20 可以用 requires 约束模板实参;C++11/14/17 则可以在类内使用 static_assert,在实例化类定义时触发编译错误。

C++20:使用 requires 约束模板

#include <concepts>
#include <string>
#include <type_traits>

template <typename T>
    requires (!std::same_as<std::remove_cvref_t<T>, std::string>)
struct AVLTree {
    // 类的实现
};

int main()
{
    AVLTree<int> tree;  // 通过此约束

    // 以下声明均因不满足约束而编译失败:
    // AVLTree<std::string> a;
    // AVLTree<const std::string> b;
    // AVLTree<std::string&> c;
    // AVLTree<const std::string&> d;
    // AVLTree<std::string&&> e;
}

这个约束分两步判断:

  1. std::remove_cvref_t<T> 去掉引用以及顶层的 const、volatile 限定。
  2. std::same_as<..., std::string> 判断处理后的类型是否与 std::string 相同,前面的 ! 表示排除相同的情况。

例如,const std::string& 处理后得到 std::string,因此不满足约束。std::remove_cvref_t 和 std::same_as 都需要 C++20。

去掉引用和 cv 限定只用于约束判断,类内部的 T 仍是调用者传入的原始类型。

C++11/14/17:使用 static_assert

项目还未使用 C++20 时,可以组合 std::remove_reference、std::remove_cv 和 std::is_same:

#include <string>
#include <type_traits>

template <typename T>
struct AVLTree {
    using UnqualifiedT =
        typename std::remove_cv<
            typename std::remove_reference<T>::type
        >::type;

    static_assert(
        !std::is_same<UnqualifiedT, std::string>::value,
        "AVLTree<T> does not support std::string, including cv/ref variants"
    );

    // 类的实现
};

int main()
{
    AVLTree<int> tree;

    // 实例化类定义时触发 static_assert:
    // AVLTree<std::string> a;
    // AVLTree<const std::string&> b;
}

这里要先去掉引用,再去掉 cv 限定。对于 const std::string&,const 限定的是被引用的对象,直接使用 std::remove_cv 无法去掉被引用类型上的 const。

两种写法的检查时机不同

类内的 static_assert 要等到实例化类定义时才会触发。仅声明指针通常不要求类是完整类型,因此使用上述 static_assert 版本时,下面的声明可以成立:

AVLTree<std::string>* ptr = nullptr;

如果随后定义 AVLTree<std::string> 对象,或者对该类型使用 sizeof,就会触发断言。

使用 requires 时,形成相应的模板特化类型就需要满足约束。因此在 C++20 版本中,上面的指针声明也会因不满足约束而编译失败。如果希望连 AVLTree<std::string>* 这样的类型用法也被拒绝,可以选择 requires。

禁止范围有哪些

上述代码判断的是去掉引用和 cv 限定后的类型是否与 std::string 相同:

  • std::string 及其 const、volatile、左值引用、右值引用组合都会被拒绝。
  • using Text = std::string; 这样的类型别名也会被拒绝,因为它没有创建新类型。
  • std::string*、std::string_view、std::wstring 以及从 std::string 派生的类型不在禁止范围内。

std::remove_cvref_t 不会去掉指针,也不会把所有字符串相关类型都转换成 std::string。这个判断适用于只需禁止题目指定的类型及其 cv/ref 变体的情况。

备注:内容仅供参考。