【问题标题】:C++ template-based "override" equivalent when partial-specializing?部分专业化时基于 C++ 模板的“覆盖”等价物?
【发布时间】:2025-12-13 04:35:01
【问题描述】:

我有一个看起来像这样的模板类/结构:

template <typename T, typename U>
struct S
{
    unsigned int operator()(T t, U u) const;
};

我想确保专业化尊重这个接口。

不幸的是,我可以用不同的返回类型专门化这个结构。例如,如果我部分专门返回 bool 而不是 unsigned int,我希望得到一个编译器错误,但编译器似乎并不关心:

template <typename T>
struct S<T,nullptr_t>
{
    bool operator()(T t, nullptr_t u) const { return 2; }
};

Example@ideone.com

在上面的例子中,专用版本应该返回2,但是由于返回类型是bool,所以返回值被转换为true,然后显示为1

为什么编译器会接受这个?

如何防止程序员使用错误的返回类型(甚至错误的参数)专门化模板?

我知道我可以使用基本模板类/结构中的虚拟方法来实现我想要的,并在子级中使用 override 关键字:

Solution with override(不编译,这很好)

但是拥有一个虚拟方法肯定会创建一个虚拟表,我想尽可能避免这种情况,特别是因为我在运行时不需要虚拟表。除非有一个技巧可以在不构建虚拟表的情况下做同样的事情?

另外,我知道如果我可以部分专业化方法或者我可以依赖非部分专业化,问题会更简单,但前者在 C++ AFAIK 中是不可能的,而后者不包括我需要的情况在我的程序中。

【问题讨论】:

  • “我怎样才能防止程序员使用错误的返回类型专门化模板”你不能。程序员可以编写与您的原始模板完全不同的特化,并随心所欲地使用它们。你唯一能做的就是确保编写的函数只接受“好的”特化。
  • 我想你可以看看en.cppreference.com/w/cpp/types/result_of,如果推断的类型是你想要的,做一些静态断言。如果错误,静态断言将停止编译并出现错误。
  • @wdudzik 我想过检查结果类型(使用decltypestd::result_of),但这意味着要么每个调用者都需要检查它,要么每个专业化都需要检查它,这有点重。也许我可以使用 C++20 的概念自动检查,但我的编译器与 C++20 不兼容。
  • 你为什么在乎?当然,人们可以写出疯狂的东西,但他们甚至可以不专门写一些不明智的东西。
  • @PasserBy 我确实关心与引入 override 关键字相同的原因:确保在编写代码时尊重接口,并且在以后更新时也尊重接口开。

标签: c++ templates overriding partial-specialization


【解决方案1】:

创建静态接口的一个好方法是使用curiously recurring template pattern。在您的情况下,它看起来像这样:

template<class Derived, class T, class U>
constexpr bool MyTrait = std::is_same<unsigned int, decltype(std::declval<Derived>()(std::declval<T>(), std::declval<U>()))>::value;

template <typename Derived, class T, class U>
struct StaticInterface
{

    unsigned int operator()(T t, U u) const{
        static_assert( MyTrait<Derived, T, U>, "errr" );

        return (*static_cast<const Derived *>(this))(std::forward<T>(t), std::forward<U>(u));
    }
};

template <typename T, typename U>
struct S : StaticInterface<S<T, U>, T, U>
{
    unsigned int operator()(T t, U u) const{ /* some implementation */}
};

template <typename T>
struct S<T, std::nullptr_t> : StaticInterface<S<T, std::nullptr_t>, T, std::nullptr_t>
{
    bool operator()(T t, std::nullptr_t u) const { return 2; }
};

要使其正常工作,必须通过如下接口完成函数调用:

template<class Derived, class T, class U>
void test(const StaticInterface<Derived, T, U> &inter){
    inter(T(), U());
}

否则,派生的运算符将被选为首选。

【讨论】:

  • OP 明确要求编译器拒绝没有正确返回类型的专业化,而您的答案并非如此
  • @Zouch 按您的要求修复
  • 这是一种有趣的方法,但它仍然允许更改返回类型而不会出现编译器错误。 ideone.com/H27dRk我想这是因为基类的operator()不参与模板解析,只有子operator()是。
  • @vdavid 自然应该通过接口来正确验证
  • @bartop 好的,所以使用中间函数它可以这样ideone.com/cJpgan 但我也可以在非 CRTP 实现中使用中间函数,例如ideone.com/kiJZHd