模板类的std::is_base_of

29

A是一个模板类时,有没有一种方法可以测试 std::is_base_of<A, B>

template <typename X, typename Y> class A {};

template <typename X> class B : public A<X, char> {};
我想要静态地测试类似于std :: is_base_of<A,B<int>>的东西,意思是BA的任何专业化中派生出来。 (为了使其更加通用,假设我们不知道B如何将A特化,即B<X>从A<X,char>派生)。
解决此问题的一种方法是从一个(非模板)类C派生出A,然后检查std :: is_base_of <C,B<int>>。但还有其他方法吗?

1
'A' 不是一种类型。你不能这样做,但你可以使用 'std::is_base_of<A<int, int>, B<int>>'。 - Arunmu
我曾经回答过类似的问题。http://stackoverflow.com/questions/34670375/how-to-enforce-template-parameter-class-to-derive-from-super-with-an-anonymous-t/34670684#34670684 - Arunmu
你认为 my_is_base_of<A, B<int>, char> 这样的用法合理吗? - TartanLlama
@TartanLlama 谢谢。但这种方法非常特定于B如何专门化A。如果可能的话,我正在寻找更一般性的解决方案。 - Saswat Padhi
@Arunmu 谢谢。我明白了,这在我的环境中可能可行。 - Saswat Padhi
4个回答

29
您可以进行以下操作:
template <template <typename...> class C, typename...Ts>
std::true_type is_base_of_template_impl(const C<Ts...>*);

template <template <typename...> class C>
std::false_type is_base_of_template_impl(...);

template <typename T, template <typename...> class C>
using is_base_of_template = decltype(is_base_of_template_impl<C>(std::declval<T*>()));

演示版

但是当基类模板有多个模板参数并且无法推断出 Ts... 时,这将无法处理多重继承或从 A 继承的私有继承。

使用 Visual Studio 2017 时,当基类模板具有多个模板参数并且无法推断出 Ts... 时,此操作将失败。

演示版

VS Bug Report

重构解决了 VS 的问题。

template < template <typename...> class base,typename derived>
struct is_base_of_template_impl
{
    template<typename... Ts>
    static constexpr std::true_type  test(const base<Ts...> *);
    static constexpr std::false_type test(...);
    using type = decltype(test(std::declval<derived*>()));
};

template < template <typename...> class base,typename derived>
using is_base_of_template = typename is_base_of_template_impl<base,derived>::type;

Live Demo


1
哦,我从来没有想到在那个上下文中可以推断出“Ts...”。太棒了! - TartanLlama
2
我得出了相同的解决方案,但是这个解决方案在从 A 单一非公共继承时也会 __失败__(而不是返回 false):( - mpark
2
@Jarod42: 谢谢!这真是挺聪明的。 - Saswat Padhi
2
似乎在is_base_of_template_impl中不需要typename T? - davidhigh
2
@Sprite:不幸的是,我们没有语法来处理类型和非类型模板参数之间的通用性。您必须创建类似于处理类型的std::array的特征。 - Jarod42
显示剩余9条评论

2

虽然有点晚了,但我想提供一种以上内容的变体。

template < template <typename...> class Base,typename Derived>
struct is_base_of_template
{
    // A function which can only be called by something convertible to a Base<Ts...>*
    // We return a std::variant here as a way of "returning" a parameter pack
    template<typename... Ts> static constexpr std::variant<Ts...> is_callable( Base<Ts...>* );

    // Detector, will return type of calling is_callable, or it won't compile if that can't be done
    template <typename T> using is_callable_t = decltype( is_callable( std::declval<T*>() ) );

    // Is it possible to call is_callable which the Derived type
    static inline constexpr bool value = std::experimental::is_detected_v<is_callable_t,Derived>;

    // If it is possible to call is_callable with the Derived type what would it return, if not type is a void
    using type = std::experimental::detected_or_t<void,is_callable_t,Derived>;
};

template < template <typename...> class Base,typename Derived> 
using is_base_of_template_t = typename is_base_of_template<Base,Derived>::type;

template < template <typename...> class Base,typename Derived>
inline constexpr bool is_base_of_template_v = is_base_of_template<Base,Derived>::value;

这里使用了提议中的is_detected机制,我认为这使测试的意图更清晰。然而,现在我可以同时获取基类实例化的类型,这对我很有用。所以我可以写成:

template <typename T, typename U> struct Foo { };

struct Bar : Foo<int,std::string> { };

static_assert( is_base_of_template_v<Foo,Bar> );

// The variant type contains the types with which the Foo base is instantiated 
static_assert( std::is_same_v<std::variant<int,std::string>,is_base_of_template_t<Foo,Bar>> );

谢谢。这很整洁! - Saswat Padhi

1
以下解决方案适用于受保护的继承。
template <template <typename...> class BaseTemplate, typename Derived, typename TCheck = void>
struct test_base_template;

template <template <typename...> class BaseTemplate, typename Derived>
using is_base_template_of = typename test_base_template<BaseTemplate, Derived>::is_base;

//Derive - is a class. Let inherit from Derive, so it can cast to its protected parents
template <template <typename...> class BaseTemplate, typename Derived>
struct test_base_template<BaseTemplate, Derived, std::enable_if_t<std::is_class_v<Derived>>> : Derived
{
    template<typename...T>
    static constexpr std::true_type test(BaseTemplate<T...> *);
    static constexpr std::false_type test(...);
    using is_base = decltype(test((test_base_template *) nullptr));
};

//Derive - is not a class, so it is always false_type
template <template <typename...> class BaseTemplate, typename Derived>
struct test_base_template<BaseTemplate, Derived, std::enable_if_t<!std::is_class_v<Derived>>>
{
    using is_base = std::false_type;
};

令人惊讶的是,在VS2017上,它可以使用相同模板进行多重继承,例如C<int>和C<float>。(不知道如何实现!)

请查看测试代码链接


你的解决方案相当不错,但它有一个小错误,请尝试测试static_assert(is_base_template_of<std::variant, std::variantstd::string>::value);。 - Anton Dyachenko

1

根据Evgeny Mamontov的答案,我认为正确的解决方案是

template <template <typename...> class BaseTemplate, typename Derived>
struct test_base_template<BaseTemplate, Derived, std::enable_if_t<std::is_class_v<Derived>>> : Derived
{
    template<typename...T>
    static constexpr std::true_type test(BaseTemplate<T...> *);
    static constexpr std::false_type test(...);
    using is_base = decltype(test((Derived *) nullptr));
};

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