C++模板化的"override"相当于什么,当部分特化时?

7

我有一个类/结构体模板,长这样:

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关键字来实现我想要的效果:

使用override的解决方案(不能编译,这很好)

但是使用虚方法肯定会创建一个虚表,并且我尽可能避免这样做,特别是因为我不需要运行时的虚表。除非有一个技巧可以在不建立虚表的情况下实现相同的功能?
此外,我知道如果我能够部分特化方法或者依赖于非部分特化的话,问题会更简单,但是据我所知前者在C++中不可能实现,后者也无法涵盖我在程序中需要的情况。

1
“我如何防止程序员使用错误的返回类型专门化模板?”你无法做到。程序员可以编写与您原始模板完全不同的专门化,并随心所欲地使用它们。唯一能做的就是确保您编写的函数只接受“好”的专门化。 - n. m.
我认为你可以看一下https://en.cppreference.com/w/cpp/types/result_of,并在推断出的类型是你想要的类型时进行一些静态断言。如果类型不正确,静态断言将停止编译并显示错误信息。 - wdudzik
我考虑检查结果类型(使用decltypestd::result_of),但这意味着每个调用者都需要检查它或每个特化都需要检查它,这有点重。也许我可以使用C++20的概念使该检查自动化,但我的编译器不兼容C++20。 - vdavid
你为什么在意呢?当然,人们可以写出疯狂的东西,但即使没有专门从事某些无意义的事情,他们也可能会这样做。 - Passer By
@PasserBy,我关心的原因与“override”关键字引入的原因相同:确保在编写代码时遵守接口,并在以后更新接口时也要遵守接口。 - vdavid
这里有一个重大的区别,带有错误签名的虚函数将会默默失败,并且它的失败条件可能取决于任意远的代码。另一方面,这通常会是编译错误,其失败条件是局部的。如果有什么问题,那就是隐式转换。 - Passer By
1个回答

2
创建静态接口的一种好方法是使用奇妙递归模板模式。在您的情况下,它应该是这样的:
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明确要求编译器拒绝没有正确返回类型的特化,而这并不是你的答案中的情况。 - cmourglia
@Zouch 已按照您的要求进行了修复。 - bartop
这是一个有趣的方法,但仍然允许更改返回类型而不会出现编译器错误。https://ideone.com/H27dRk 我认为这是因为基类的operator()没有参与模板解析,只有子类的operator()参与了。 - vdavid
@vdavid 自然应该通过接口使用以正确验证。 - bartop
@bartop 好的,所以通过一个中间函数它可以这样实现 https://ideone.com/cJpgan 但是在非CRTP实现中我也可以使用一个中间函数来完成相同的操作,例如 https://ideone.com/kiJZHd - vdavid
@vdavid 这是一个例子。我的意思是让函数使用接口,而不是具体类型。这将确保正确的运算符签名。 - bartop

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接