c++ 防止模板类的多个派生具有相同的参数值?

ryevplcw  于 2023-06-25  发布在  其他
关注(0)|答案(1)|浏览(144)

我有一个继承层次结构,它使用CRTP进行静态多态。模板参数中的一个是整数值,我想确保这个数字在所有派生中是唯一的,这样就不允许出现以下情况:

template<class R, quint16 N>
class Base
{...};

class DerA : Base<DerA, 10>
{...};

class DerB : Base<DerB, 10>
{...}; // Error - 10 is already in-use

当然,通过static_assert()进行错误操作会更好,这样错误信息就很清楚了,但是任何编译的预防都是可以的。
我认为这可能通过某种类型的traits类来实现,但这很棘手,因为基类实际上并不被派生类共享,因为根据模板参数,基类将是一个不同的类,如果我使用共享的非模板基类,那么它根本无法再与模板参数交互。鉴于我缺乏模板元编程经验,这对我来说绝对是进入了“《双城之战》”领域。
如果可以这样做,但只能通过创建派生类本身以外的附加代码(即通过一个宏),这也是好的,但前提是如果所述代码丢失,也可以防止编译。所以从本质上讲,没有什么可以很容易被遗忘,没有立即的迹象表明有什么是错误的。这也不能依赖于提前知道N的所有值,因为确切的推导不是固定的/在用户代码中是可扩展的。
如果有关系的话,目标是让派生类表示不同的State/Error类型,每个类型都有一个与它们相关联的保证唯一的“代码”,可以通过静态成员和虚方法访问。我相信我可以在运行时开始时通过一个额外的非模板基来实现这种检查,该非模板基具有SIOF保护的std::set,派生类通过模板基中的静态成员自动注册到该静态成员中,该静态成员在其构造函数中调用insert(具有保护contains检查),但在编译时以某种方式处理这个当然是首选。

f2uvfpb9

f2uvfpb91#

有一件事阻止你在编译时这样做:在不同的翻译单元中定义派生类的可能性。
这样就有可能使用链接器进行这种检查。然而,由于可能涉及匿名名称空间,并且可能涉及不同类型的库,因此很难确保这样的约束。
我的建议是使用“代码信息生成工具”来实现这个目的。事实上,由于编译器需要输出static_assert的第二个参数作为错误消息的一部分,所以当使用指定的预处理器符号进行编译时,您可以使用此功能从代码中提取所需的信息。这当然要求您了解所有可能包含子类定义的翻译单元。
如果在所有地方使用以下代码来生成用于此目的的子类:

base.hpp

#ifndef BASE_HPP
#define BASE_HPP

#include <algorithm>
#include <limits>
#include <string_view>
#include <type_traits>

namespace my_types
{
using IdType = int;

namespace impl
{
constexpr bool IsValidId(std::string_view sv)
{
    using namespace std::string_view_literals;

    static_assert((std::numeric_limits<IdType>::max)() == 2147483647);
    constexpr auto MaxInt = "2147483647"sv;

    if (sv.empty())
    {
        return false;
    }
    if (sv[0] == '0')
    {
        // exclude oct literals
        return false;
    }

    for (auto& c : sv)
    {
        if ((c < '0') && (c > '9'))
        {
            return false;
        }
    }

    return ((sv.size() < MaxInt.size()) || ((sv.size() == MaxInt.size()) && (sv < MaxInt)));
}
} // namespace impl

template<class R, IdType N>
class Base
{
};

} // namespace my_types

#ifdef EXTRACT_IDS
#   define EXTRACT_ID_SUBSTR "TYPE_EXTRATION_ID"
#   define EXTRACT_ID(x,n) static_assert(false, EXTRACT_ID_SUBSTR "_" #x "|" #n "_" EXTRACT_ID_SUBSTR);
#else
#   define EXTRACT_ID(x,n)
#endif

#define DEFINE_DERIVED_TYPE(Name, n)                                                    \
static_assert(::my_types::impl::IsValidId(#n), "'" #n "' is not a integral literal");   \
EXTRACT_ID(Name,n)                                                                      \
class Name : ::my_types::Base<Name, n>

#endif // BASE_HPP

a.cpp

#include "base.hpp"

namespace
{
DEFINE_DERIVED_TYPE(DerA, 10)
{
};
}

*B.cpp

#include "base.hpp"

namespace
{
DEFINE_DERIVED_TYPE(DerB, 10)
{
};
}

makefile

CXX=/usr/bin/g++
CXX_FLAGS= -std=c++17

SOURCES=a.cpp b.cpp

code_test.cpp: $(SOURCES)
    (($(CXX) -DEXTRACT_IDS $(CXX_FLAGS) -o dummy $(SOURCES) 2>&1 || exit 0) | \
        sed -n 's/.*TYPE_EXTRATION_ID_\(.*\)|\([0-9][0-9]*\)_TYPE_EXTRATION_ID.*/#ifdef _CODE_\2\n _CODE_\2(\1)\n#else\n#define _CODE_\2(x) static_assert(false, "duplicate code \2 for class " #x ": was previously defined for class \1");\n#endif/p' ; \
        echo "") > code_test.cpp

main: $(SOURCES) codes.hpp code_test.cpp
    $(CXX) $(CXX_FLAGS) -o main $(SOURCES) code_test.cpp

all: main

然而,您可能更好地在运行时收集派生类的全局代码集合,可能仅限于调试配置,以触发重复值的assert s...

相关问题